Почему табличная переменная вызывает сканирование индекса, в то время как временная таблица использует поиск и поиск по закладкам?

18

Я пытаюсь понять, почему использование табличной переменной не позволяет оптимизатору использовать поиск по индексу, а затем поиск по закладкам по сравнению со сканированием индекса.

Заполнение таблицы:

CREATE TABLE dbo.Test 
(
    RowKey INT NOT NULL PRIMARY KEY, 
    SecondColumn CHAR(1) NOT NULL DEFAULT 'x',
    ForeignKey INT NOT NULL 
) 

INSERT dbo.Test 
(
    RowKey, 
    ForeignKey
) 
SELECT TOP 1000000 
    ROW_NUMBER() OVER (ORDER BY (SELECT 0)),
    ABS(CHECKSUM(NEWID()) % 10)     
FROM sys.all_objects s1
CROSS JOIN sys.all_objects s2 

CREATE INDEX ix_Test_1 ON dbo.Test (ForeignKey) 

Заполните табличную переменную одной записью и попытайтесь найти первичный ключ и второй столбец, выполнив поиск по столбцу внешнего ключа:

DECLARE @Keys TABLE (RowKey INT NOT NULL) 

INSERT @Keys (RowKey) VALUES (10)

SELECT 
    t.RowKey,
    t.SecondColumn
FROM
    dbo.Test t 
INNER JOIN 
    @Keys k
ON
    t.ForeignKey = k.RowKey

Ниже приведен план выполнения:

введите описание изображения здесь

Теперь тот же запрос, используя вместо этого временную таблицу:

CREATE TABLE #Keys (RowKey INT NOT NULL) 

INSERT #Keys (RowKey) VALUES (10) 

SELECT 
    t.RowKey,
    t.SecondColumn
FROM
    dbo.Test t 
INNER JOIN 
    #Keys k
ON
    t.ForeignKey = k.RowKey

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

введите описание изображения здесь

Почему оптимизатор хочет выполнить поиск закладок с помощью временной таблицы, но не табличной переменной?

Переменная table используется в этом примере для представления данных, поступающих через определенный пользователем тип таблицы в хранимой процедуре.

Я понимаю, что поиск по индексу может быть неуместным, если значение внешнего ключа встречалось сотни тысяч раз. В этом случае, сканирование, вероятно, будет лучшим выбором. Для сценария, который я создал, не было строки со значением 10. Я все еще думаю, что поведение интересно, и хотел бы знать, есть ли причина для этого.

SQL Fiddle

Добавление OPTION (RECOMPILE)не изменило поведение. У UDDT есть первичный ключ.

@@VERSION такое SQL Server 2008 R2 (SP2) - 10.50.4042.0 (X64) (сборка 7601: пакет обновления 1) (гипервизор)

8kb
источник

Ответы:

15

Причиной такого поведения является то, что SQL Server не может определить, сколько строк будет соответствовать ForeignKey, поскольку нет индекса с RowKey в качестве ведущего столбца (это можно сделать из статистики в таблице #temp, но это не так. существует для табличных переменных / UDTT), поэтому он оценивает 100 000 строк, что лучше обрабатывать при сканировании, чем при поиске + поиске. К тому времени, когда SQL Server поймет, что есть только одна строка, уже слишком поздно.

Вы могли бы построить свой UDTT по-другому; в более современных версиях SQL Server вы можете создавать вторичные индексы для табличных переменных, но этот синтаксис недоступен в 2008 R2.

Кстати, вы можете получить поведение поиска (по крайней мере, в моих ограниченных испытаниях), если попытаетесь избежать растрового изображения / зонда, намекая на соединение с вложенными циклами:

DECLARE @Keys TABLE (RowKey INT PRIMARY KEY); -- can't hurt

INSERT @Keys (RowKey) VALUES (10);

SELECT 
     t.RowKey
    ,t.SecondColumn
FROM
    dbo.Test t 
INNER JOIN 
    @Keys k
ON
    t.ForeignKey = k.RowKey
    OPTION (LOOP JOIN);

Я научился этому трюку у Пола Уайта несколько лет назад. Конечно, вы должны быть осторожны, вставляя любые подсказки в производственный код - это может потерпеть неудачу, если люди вносят изменения в базовые объекты и этот конкретный тип объединения больше не возможен или более не оптимален.

Для более сложных запросов и при переходе на SQL Server 2012 или более позднюю версию может оказаться полезным флаг трассировки 2453 . Однако этот флаг не помог с этим простым соединением. И то же самое заявление об отказе от ответственности - это просто альтернативная вещь, которую вы обычно не должны делать без тонны документации и строгих процедур регрессионного тестирования.

Кроме того, Service Pack 1 давно не поддерживается, вы должны получить Service Pack 3 + MS15-058 .

Аарон Бертран
источник
3

Табличные переменные и временные таблицы обрабатываются по-разному различными способами. Здесь есть отличный ответ с большим количеством деталей относительно того, где они отличаются.

В частности, в вашем случае я бы предположил, что виновником является тот факт, что временные таблицы могут генерировать дополнительную статистику и параллельные планы, в то время как переменные таблицы имеют более ограниченную статистику (без статистики на уровне столбцов), а параллельные планы не являются вашим виновником.

Вам может быть лучше сбросить табличную переменную во временную таблицу на время хранимой процедуры.

Кеннет Фишер
источник