Почему оптимизатор выбрал бы Clustered Index + Sort вместо Non-Clustered Index?

11

Учитывая следующий пример:

IF OBJECT_ID('dbo.my_table') IS NOT NULL
    DROP TABLE [dbo].[my_table];
GO

CREATE TABLE [dbo].[my_table]
(
    [id]    int IDENTITY (1,1)  NOT NULL PRIMARY KEY,
    [foo]   int                 NULL,
    [bar]   int                 NULL,
    [nki]   int                 NOT NULL
);
GO

/* Insert some random data */
INSERT INTO [dbo].[my_table] (foo, bar, nki)
SELECT TOP (100000)
    ABS(CHECKSUM(NewId())) % 14,
    ABS(CHECKSUM(NewId())) % 20,
    n = CONVERT(INT, ROW_NUMBER() OVER (ORDER BY s1.[object_id]))
FROM 
    sys.all_objects AS s1 
CROSS JOIN 
    sys.all_objects AS s2
GO

CREATE UNIQUE NONCLUSTERED INDEX [IX_my_table]
    ON [dbo].[my_table] ([nki] ASC);
GO

Если я получу все записи, упорядоченные по [nki](некластеризованный индекс):

SET STATISTICS TIME ON;
SELECT id, foo, bar, nki FROM my_table ORDER BY nki;
SET STATISTICS TIME OFF;

SQL Server Execution Times: CPU time = 266 ms, elapsed time = 493 ms

Оптимизатор выбирает кластеризованный индекс, а затем применяет алгоритм сортировки.

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

Execution plan

Но если я заставлю его использовать некластеризованный индекс:

SET STATISTICS TIME ON;
SELECT id, foo, bar, nki FROM my_table WITH(INDEX(IX_my_TABLE));
SET STATISTICS TIME OFF;

SQL Server Execution Times: CPU time = 311 ms, elapsed time = 188 ms

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

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

Execution plan

Очевидно, что если некластеризованный индекс преобразуется в покрывающий индекс:

CREATE UNIQUE NONCLUSTERED INDEX [IX_my_table]
    ON [dbo].[my_table] ([nki] ASC)
    INCLUDE (id, foo, bar);
GO

Тогда он использует только этот индекс:

SET STATISTICS TIME ON;
SELECT id, foo, bar, nki FROM my_table ORDER BY nki;
SET STATISTICS TIME OFF;

SQL Server Execution Times: CPU time = 32 ms, elapsed time = 106 ms

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

Execution plan


Вопрос

  • Почему SQL Server использует кластеризованный индекс плюс алгоритм сортировки вместо некластеризованного индекса, даже если время выполнения в последнем случае увеличивается на 38%?
McNets
источник
1
Вы хотели оставить запрос ORDER BY в запросе принудительного индекса?
Форрест

Ответы:

9

Почему SQL Server использует кластеризованный индекс плюс алгоритм сортировки вместо некластеризованного индекса, даже если время выполнения в последнем случае увеличивается на 38%?

Поскольку SQL Server использует оптимизатор на основе затрат на основе статистики, а не информации времени выполнения.

Во время процесса оценки стоимости для этого запроса он действительно оценивает план поиска, но оценивает, что он потребует больше усилий. (Обратите внимание на «Оценочную стоимость поддерева» при наведении курсора на SELECT в плане выполнения). Это тоже не обязательно плохое предположение - на моей тестовой машине план поиска занимает в 6 раз больше процессора сортировки / сканирования.

Посмотрите на ответ Роба Фарли о том, почему SQL Server может стоить план поиска выше.

Форрест
источник
9

Если бы вы сравнили количество операций чтения, требуемых в 100 000 поисков, с тем, что связано с выполнением сортировки, вы могли бы быстро получить представление о том, почему Query Optimizer считает, что сортировка CIX + будет лучшим выбором.

Выполнение Lookup заканчивается быстрее, потому что читаемые страницы находятся в памяти (даже если вы очищаете кеш, у вас много строк на страницу, поэтому вы читаете одни и те же страницы снова и снова, но с разной степенью фрагментации или другое давление памяти от другой деятельности, это может быть не так). На самом деле сортировка CIX + Sort не займет столько времени, но вы видите, что стоимость чтения не учитывает относительную дешевизну повторного посещения одних и тех же страниц.

Роб Фарли
источник
4

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

Как следует из комментариев Джона Эйсбренера , одна из наиболее упоминаемых, даже в других блогах, это интересная статья Кимберли Л. Триппа:

но это не единственный, если вы заинтересованы, вы можете взглянуть на эти страницы:

Как видите, все они движутся вокруг концепции переломного момента .

Цитируется из статьи К.Л. Триппа

Какой переломный момент?

Это точка, в которой количество возвращаемых строк « недостаточно селективно ». SQL Server выбирает НЕ использовать некластеризованный индекс для поиска соответствующих строк данных и вместо этого выполняет сканирование таблицы.

Когда SQL Server использует некластеризованный индекс в куче, в основном он получает список указателей на страницы базовой таблицы. Затем он использует эти указатели для извлечения строк с помощью ряда операций, называемых Row ID Lookups (RID). Это означает, что, по крайней мере, он будет использовать столько же чтений страниц, сколько возвращенных строк, а возможно, и больше. Этот процесс несколько похож на кластерный индекс в качестве базовой таблицы, с тем же результатом: больше операций чтения.

Но когда наступит этот переломный момент?

Конечно, как и большинство вещей в этой жизни, это зависит ...

Не серьезно, это происходит между 25% и 33% от числа страниц в таблице, в зависимости от того, сколько строк на странице. Но есть и другие факторы, которые вы должны учитывать:

Цитируется из статьи ITPRoToday

Другие факторы, влияющие на переломный момент Хотя стоимость поисков RID является наиболее важным фактором, влияющим на переломный момент, существует ряд других факторов:

  • Физический ввод-вывод намного эффективнее при сканировании кластерного индекса. Данные кластеризованного индекса последовательно размещаются на диске в порядке индекса. Следовательно, на диске очень мало бокового перемещения головки, что улучшает производительность ввода-вывода.
  • Когда ядро ​​базы данных сканирует кластерный индекс, оно знает, что существует высокая вероятность того, что следующие несколько страниц на дорожке диска будут по-прежнему содержать данные, которые ему необходимы. Таким образом, он начинает читать вперед в кусках по 64 КБ вместо обычных страниц по 8 КБ. Это также приводит к более быстрому вводу / выводу.

Теперь, если я выполню свои запросы снова, используя статистику ввода-вывода:

SET STATISTICS IO ON;
SELECT id, foo, bar, nki FROM my_table WHERE nki < 20000 ORDER BY nki ;
SET STATISTICS IO OFF;

Logical reads: 312

SET STATISTICS IO ON;
SELECT id, foo, bar, nki FROM my_table WITH(INDEX(IX_my_TABLE));
SET STATISTICS IO OFF;

Logical reads: 41293

Второй запрос требует больше логических чтений, чем первый.

Должен ли я избегать некластеризованного индекса?

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

Цитируется из статьи К.Л. Триппа

Итак, что нужно сделать? По-разному. Если вы хорошо знаете свои данные и проводите всестороннее тестирование, вы можете подумать об использовании подсказки (есть некоторые умные вещи, которые вы можете сделать программно в sps, я постараюсь посвятить этому пост в ближайшее время). Тем не менее, гораздо лучший выбор (если это вообще возможно) заключается в рассмотрении покрытия (это действительно моя главная мысль :). В моих запросах покрытие нереально, потому что мои запросы требуют все столбцы (зло SELECT *), но, если ваши запросы уже и они имеют высокий приоритет, вам лучше использовать индекс покрытия (во многих случаях) вместо подсказки, потому что индекс, который охватывает запрос, а не советы.

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

McNets
источник