Выражение запроса с использованием другого синтаксиса может иногда помочь оптимизатору сообщить о вашем желании использовать некластеризованный индекс. Вы должны найти форму ниже дает вам план, который вы хотите:
SELECT
[ID],
[DeviceID],
[IsPUp],
[IsWebUp],
[IsPingUp],
[DateEntered]
FROM [dbo].[Heartbeats]
WHERE
[ID] IN
(
-- Keys
SELECT TOP (1000)
[ID]
FROM [dbo].[Heartbeats]
WHERE
[DateEntered] >= CONVERT(datetime, '2011-08-30', 121)
AND [DateEntered] < CONVERT(datetime, '2011-08-31', 121)
);
Сравните этот план с планом, созданным, когда некластеризованный индекс форсируется с подсказкой:
SELECT TOP (1000)
*
FROM [dbo].[Heartbeats] WITH (INDEX(CommonQueryIndex))
WHERE
[DateEntered] BETWEEN '2011-08-30' and '2011-08-31';
Планы по сути одинаковы (поиск ключей - не что иное, как поиск по кластерному индексу). Обе формы плана будут когда-либо выполнять только один поиск по некластеризованному индексу и максимум 1000 поисков в кластеризованном индексе.
Важным отличием является положение оператора Top. Расположенный между двумя поисками, Top не позволяет оптимизатору заменить две операции поиска логически эквивалентным сканированием кластеризованного индекса. Оптимизатор работает, заменяя части логического плана эквивалентными реляционными операциями. Top не является реляционным оператором, поэтому перезапись предотвращает преобразование в просмотр кластеризованного индекса. Если бы оптимизатор мог изменить положение оператора Top, он все равно предпочел бы сканирование, а не поиск + поиск из-за способа оценки затрат.
Калькуляция сканирования и поиска
На очень высоком уровне модель затрат оптимизатора для сканирования и поиска довольно проста: она оценивает, что 320 случайных операций поиска стоят столько же, сколько считывание 1350 страниц при сканировании. Это, вероятно, мало похоже на аппаратные возможности какой-либо конкретной современной системы ввода / вывода, но работает достаточно хорошо в качестве практической модели.
Модель также делает ряд упрощающих допущений, главное из которых состоит в том, что каждый запрос должен начинаться без данных или страниц индекса, уже находящихся в кеше. Подразумевается, что каждый ввод-вывод будет приводить к физическому вводу-выводу, хотя на практике это случается редко. Даже с холодным кэшем предварительная выборка и упреждающее чтение означают, что необходимые страницы на самом деле вполне могут быть в памяти к тому времени, когда они нужны обработчику запросов.
Другое соображение заключается в том, что первый запрос строки, которой нет в памяти, приведет к тому, что вся страница будет извлечена с диска. Последующие запросы на строки на той же странице, скорее всего, не повлекут за собой физического ввода-вывода. Модель калькуляции содержит логику для учета некоторых эффектов, подобных этой, но она не идеальна.
Все это (и даже больше) означает, что оптимизатор имеет тенденцию переключаться на сканирование раньше, чем, вероятно, следует. Случайный ввод-вывод только «намного дороже», чем «последовательный» ввод-вывод, если возникает физическая операция - доступ к страницам в памяти действительно очень быстрый. Даже там, где требуется физическое чтение, сканирование может вообще не приводить к последовательному чтению из-за фрагментации, и поиски могут быть расположены так, что шаблон по существу является последовательным. Добавьте к этому изменяющуюся характеристику производительности современных систем ввода / вывода (особенно твердотельных), и все это начинает выглядеть очень шатко.
Ряд голов
Присутствие главного оператора в плане изменяет подход к затратам. Оптимизатор достаточно умен, чтобы знать, что для поиска 1000 строк с помощью сканирования, скорее всего, не потребуется сканирование всего кластерного индекса - он может остановиться, как только будет найдено 1000 строк. Он устанавливает «целевую строку» в 1000 строк в операторе Top и использует статистическую информацию для обратной работы, чтобы оценить, сколько строк она ожидает от источника строки (в данном случае - сканирование). Я написал о деталях этого расчета здесь .
Изображения в этом ответе были созданы с помощью SQL Sentry Plan Explorer .