Сортування розливів до tempdb за рахунок varchar (max)


10

На сервері з 32 ГБ ми запускаємо SQL Server 2014 SP2 з максимальною пам'яттю 25 ГБ, у нас є дві таблиці, тут ви знайдете спрощену структуру обох таблиць:

CREATE TABLE [dbo].[Settings](
    [id] [int] IDENTITY(1,1) NOT NULL,
    [resourceId] [int] NULL,
    [typeID] [int] NULL,
    [remark] [varchar](max) NULL,
    CONSTRAINT [PK_Settings] PRIMARY KEY CLUSTERED ([id] ASC)
) ON [PRIMARY]
GO

CREATE TABLE [dbo].[Resources](
    [id] [int] IDENTITY(1,1) NOT NULL,
    [resourceUID] [int] NULL,
 CONSTRAINT [PK_Resources] PRIMARY KEY CLUSTERED ([id] ASC)
) ON [PRIMARY]
GO

із наступними некластеризованими індексами:

CREATE NONCLUSTERED INDEX [IX_UID] ON [dbo].[Resources]
(
    [resourceUID] ASC
)

CREATE NONCLUSTERED INDEX [IX_Test] ON [dbo].[Settings]
(
    [resourceId] ASC,
    [typeID] ASC
)

База даних налаштована на compatibility level120.

Коли я запускаю цей запит , є розливи tempdb. Ось як я виконую запит:

exec sp_executesql N'
select r.id,remark
FROM Resources r
inner join Settings on resourceid=r.id
where resourceUID=@UID
ORDER BY typeID',
N'@UID int',
@UID=38

Якщо не вибрати [remark]поле, розливу не відбувається. Моя перша реакція полягала в тому, що розливи відбулися через малу кількість оцінених рядків на операторі вкладеного циклу.

Тому я додаю 5 таблиць часу та 5 цілих стовпців до таблиці налаштувань і додаю їх до мого вибору. Коли я виконую запит, розливу не відбувається.

Чому розливи трапляються лише тоді, коли [remark]вибрано? Це, мабуть, пов'язане з тим, що це а varchar(max). Що я можу зробити, щоб не розпливатися tempdb?

Додавання OPTION (RECOMPILE)запиту не має значення.


Можливо, ви можете спробувати select r.id, LEFT(remark, 512)(або будь-яка розумна довжина підрядки).
мустаччо

@Forrest: Я намагаюся відтворити дані, необхідні для моделювання проблеми. На перший погляд це стосується низької оцінки вкладеного циклу. За моїми фіктивними даними, орієнтовна кількість рядків набагато більша, і проливання не відбувається
Фредерік Вандерхаген

Відповіді:


10

Тут буде кілька можливих шляхів вирішення.

Ви можете налаштувати грант пам'яті вручну , хоча я, мабуть, не пішов би по цьому маршруту.

Ви також можете використовувати CTE і TOP, щоб натиснути сорт нижче, перш ніж захопити стовпчик максимальної довжини. Це буде виглядати приблизно як нижче.

WITH CTE AS (
SELECT TOP 1000000000 r.ID, s.ID AS ID2, s.typeID
FROM Resources r
inner join Settings s on resourceid=r.id
where resourceUID=@UID
ORDER BY s.typeID
)
SELECT c.ID, ca.remark
FROM CTE c
CROSS APPLY (SELECT remark FROM dbo.Settings s WHERE s.id = c.ID2) ca(remark)
ORDER BY c.typeID

Перевірка концепції dbfiddle тут . Зразок даних все-таки буде вдячний!

Якщо ви хочете прочитати відмінний аналіз Пола Уайта, читайте тут.


7

Чому розливи трапляються лише тоді, коли вибрано [зауваження]?

Розлив відбувається, коли ви включаєте цей стовпець, тому що ви не отримуєте достатньо великого обсягу пам'яті для сортування великих рядкових даних.

Ви не отримаєте достатньо великого розміру пам’яті, оскільки фактична кількість рядків на 10 разів більше, ніж передбачувана кількість рядків (1 302 фактичних проти 126 оцінок).

Чому відрахування? Чому SQL Server вважає, що у dbo є лише один рядок. Налаштування з resourceid38?

Це може бути проблема зі статистикою, яку ви можете перевірити, запустивши DBCC SHOW_STATISTICS('dbo.Settings', 'IX_Test')і побачити рахунки за цей крок гістограми. Але план виконання, схоже, вказує на те, що статистика настільки ж повна та сучасна, як і раніше.

Оскільки статистика не допомагає, найкраща ставка - це, мабуть, перезапис запитів - про що Форест висвітлив у своїй відповіді.


3

Мені здається, що whereпункт у запиті задає питання, і є причиною низьких оцінок, навіть якщо OPTION(RECOMPILE)він використовується.

Я створив кілька тестових даних, і врешті-решт придумав два рішення, зберігаючи IDполе з resourcesбудь-якої змінної (якщо вона завжди унікальна), або таблиці з тимчасовою таблицею, якщо у нас може бути більше одного ID.

Базові записи тестів

SET NOCOUNT ON
DECLARE @i int= 1;
WHILE @i <= 10000
BEGIN
INSERT INTO [dbo].[Settings]([resourceId],[typeID],remark)
VALUES(@i,@i,'KEPT THESE VALUES OUT BECAUSE IT WOULD CLUTTER THE EXAMPLES, VALUES OVER 8000 Chars entered here'); -- 23254 character length on each value
INSERT INTO  [dbo].[Resources](resourceUID)
VALUES(@i);
SET @i += 1;
END

Вставте значення "Шукати", щоб дійти до того ж приблизного набору результатів, що і ОП (1300 записів)

INSERT INTO  [dbo].[Settings]([resourceId],[typeID],remark)
VALUES(38,38,'KEPT THESE VALUES OUT BECAUSE IT WOULD CLUTTER THE EXAMPLES, VALUES OVER 8000 Chars entered here')
GO 1300

Змініть компакт-дані та оновити статистику відповідно до ОП

ALTER DATABASE StackOverflow SET COMPATIBILITY_LEVEL = 120;
UPDATE STATISTICS settings WITH FULLSCAN;
UPDATE STATISTICS resources WITH FULLSCAN;

Оригінальний запит

exec sp_executesql N'
select r.id
FROM Resources r
inner join Settings on resourceid=r.id
where resourceUID=@UID
ORDER BY typeID',
N'@UID int',
@UID=38

Мої оцінки ще гірші : один розрахунковий ряд, тоді як 1300 повертаються. І як заявлено в ОП, не має значення, якщо я додаюOPTION(RECOMPILE)

Важливе, що слід зазначити, це те, що коли ми позбуємось пункту де, оцінки є на 100% правильними, що очікується, оскільки ми використовуємо всі дані в обох таблицях.

Я змусив індекси просто переконатися, що ми використовуємо ті самі, що і в попередньому запиті, щоб довести точку

exec sp_executesql N'
select r.id,remark
FROM Resources r with(index([IX_UID]))
inner join Settings WITH(INDEX([IX_Test])) 
on resourceid=r.id
ORDER BY typeID',
N'@UID int',
@UID=38

Як і очікувалося, хороші оцінки.

Отже, що ми могли б змінити, щоб отримати кращі оцінки, але все ж шукати свої цінності?

ЯКЩО @UID унікальний, як у прикладі OP дано, ми могли б поставити сингл, idякий повернувся з resourcesзмінної, а потім шукати цю змінну за допомогою OPTION (RECOMPILE)

DECLARE @UID int =38 , @RID int;
SELECT @RID=r.id from 
Resources r where resourceUID = @UID;

SELECT @uid, remark 
from Settings 
where resourceId = @uid 
Order by typeID
OPTION(RECOMPILE);

Що дає 100% точні оцінки

Але що робити, якщо в ресурсах є кілька ресурсів UID?

додати деякі дані тесту

INSERT INTO Resources(ResourceUID)
VALUES (38);
go 50

Це можна вирішити за допомогою темп-таблиці

CREATE TABLE #RID (id int)
DECLARE @UID int =38 
INSERT INTO #RID
SELECT r.id 
from 
Resources r where resourceUID = @UID

SELECT @uid, remark 
from Settings  s
INNER JOIN #RID r
ON r.id =s.resourceId
Order by typeID
OPTION(RECOMPILE)

DROP TABLE #RID

Знову з точними оцінками .

Це було зроблено за допомогою мого власного набору даних, YMMV.


Написано з sp_executesql

Зі змінною

exec sp_executesql N'
DECLARE  @RID int;
    SELECT @RID=r.id from 
    Resources r where resourceUID = @UID;

    SELECT @uid, remark 
    from Settings 
    where resourceId = @uid 
    Order by typeID
    OPTION(RECOMPILE);',
N'@UID int',
@UID=38

З темп-таблицею

exec sp_executesql N'

CREATE TABLE #RID (id int)

INSERT INTO #RID
SELECT r.id 
from 
Resources r where resourceUID = @UID

SELECT @uid, remark 
from Settings  s
INNER JOIN #RID r
ON r.id =s.resourceId
Order by typeID
OPTION(RECOMPILE)

DROP TABLE #RID',
N'@UID int',
@UID=38

Все-таки 100% правильні оцінки на моєму тесті

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.