Сортировка разливов в 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)(или любой разумной длины подстроки может быть).
mustaccio

@Forrest: я пытаюсь воспроизвести данные, необходимые для моделирования проблемы. На первый взгляд это связано с низкой оценкой вложенного цикла. По моим фиктивным данным, предполагаемое количество строк намного выше, и
Frederik

Ответы:


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 раз больше, чем предполагаемое количество строк (1302 фактических против 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

Вставьте значения «Поиск», чтобы получить тот же приблизительный набор результатов, что и OP (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

Изменить статистику для сравнения и обновления, чтобы соответствовать OP

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)

Важно отметить, что когда мы избавляемся от предложения where, оценки оказываются на 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% точные оценки

Но что если в ресурсах есть несколько resourceUID?

добавить некоторые тестовые данные

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.