Почему этот запрос не использует катушку индекса?

23

Я задаю этот вопрос, чтобы лучше понять поведение оптимизатора и понять ограничения вокруг катушек индекса. Предположим, что я положил целые числа от 1 до 10000 в кучу:

CREATE TABLE X_10000 (ID INT NOT NULL);
truncate table X_10000;

INSERT INTO X_10000 WITH (TABLOCK)
SELECT TOP 10000 ROW_NUMBER() OVER (ORDER BY (SELECT NULL))
FROM master..spt_values t1
CROSS JOIN master..spt_values t2;

И принудительно включите вложенный цикл MAXDOP 1:

SELECT *
FROM X_10000 a
INNER JOIN X_10000 b ON a.ID = b.ID
OPTION (LOOP JOIN, MAXDOP 1);

Это довольно недружественное действие по отношению к SQL Server. Соединения с вложенными циклами часто не являются хорошим выбором, когда обе таблицы не имеют соответствующих индексов. Вот план:

плохой запрос

Запрос занимает 13 секунд на моей машине с 100000000 строк, извлеченных из буфера таблицы. Однако я не понимаю, почему запрос должен быть медленным. Оптимизатор запросов имеет возможность создавать индексы на лету с помощью катушек индексов . Этот запрос выглядит как идеальный кандидат на катушку индекса.

Следующий запрос возвращает те же результаты, что и первый, имеет катушку индекса и завершается менее чем за секунду:

SELECT *
FROM X_10000 a
CROSS APPLY (SELECT TOP (9223372036854775807) b.ID FROM X_10000 b WHERE a.ID = b.ID) ca
OPTION (LOOP JOIN, MAXDOP 1);

Обходной путь 1

Этот запрос также имеет катушку индекса и завершается менее чем за секунду:

SELECT *
FROM X_10000 a
INNER JOIN X_10000 b ON a.ID >= b.ID AND a.ID <= b.ID
OPTION (LOOP JOIN, MAXDOP 1);

Обходной путь 2

Почему исходный запрос не имеет катушку индекса? Существует ли какой-либо набор документированных или недокументированных подсказок или флагов трассировки, которые дадут ему катушку индекса? Я нашел этот связанный вопрос , но он не полностью отвечает на мой вопрос, и я не могу заставить загадочный флаг трассировки работать для этого запроса.

Джо Оббиш
источник

Ответы:

20

Как вы знаете, поиск оптимизатора не является исчерпывающим. Он пробует вещи, которые имеют смысл в контексте, и которые часто приносят дивиденды по реальным запросам. Принудительное соединение цикла между двумя одноэлементными неиндексированными таблицами кучи - не такой сценарий. Тем не менее, вот некоторые детали:

SQL Server любит преобразовывать, применяется к объединениям рано, потому что он знает больше хитростей с объединениями. Позже он может изучить возможность преобразования соединения обратно в заявку. Различие между этими двумя коррелируют параметрами (внешние ссылки). Применения имеют смысл, когда на внутренней стороне есть подходящий индекс. В вашем примере нет индексов, поэтому оптимизатор не убежден изучать перевод в заявку.

Простое (неприменимое) соединение имеет предикат объединения в операторе соединения вместо внешних ссылок. Оптимизация буфера для неприменения обычно является ленивой буферизацией таблицы, так как нет предиката на внутренней стороне, только в соединении.

Оптимизатор не рассматривает возможность создания индекса на лету, чтобы разрешить применение; скорее последовательность событий обычно обратная: преобразование применяется, потому что существует хороший индекс.

Иногда вы можете поощрять применение, а не объединение, используя APPLYсинтаксис в своем запросе. Недокументированный флаг 9114 трассировки может помочь в этом, отговаривая оптимизатора от преобразования логического применения к объединению заранее. Например:

SELECT * 
FROM dbo.X_1000 AS a
CROSS APPLY (SELECT * FROM dbo.X_1000 AS b WHERE b.ID = a.ID) AS b
OPTION (QUERYTRACEON 9114);

Спул план

Золотник индекса предпочтителен для применения, потому что внешняя ссылка означает, что выбор применяется на внутренней стороне соединения. Вы будете часто видеть это через, SelToIndexOnTheFlyно существуют другие пути. См. Мою статью «Стремительный индекс» и «Оптимизатор» .

Пол Уайт говорит, что GoFundMonica
источник