Чому оптимізатор вибирає кластерний індекс + сортування замість некластеризованого індексу?


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% швидше?

1
Ви мали на увазі залишити ЗАМОВЛЕННЯ у запиті з примусовим індексом?
Форрест

Відповіді:


9

Чому SQL Server використовує кластерний індекс плюс алгоритм сортування замість некластеризованого індексу, навіть якщо в останньому випадку час виконання на 38% швидше?

Оскільки SQL Server використовує оптимізатор на основі витрат, заснований на статистиці, а не інформації про час виконання.

Під час процесу оцінки цього запиту він фактично оцінює план пошуку, але, за його оцінками, знадобиться більше зусиль. (Зверніть увагу на "Орієнтовну вартість піддіаграму", коли наведіть курсор миші на SELECT у плані виконання). Це не обов'язково і погане припущення - на моїй тестовій машині план пошуку займає 6X процесор сортування / сканування.

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


9

Якщо ви порівнюєте кількість прочитаних, необхідних у 100 000 пошукових запитів, з тим, що пов'язано з тим, що робиться в роді, ви можете швидко зрозуміти, чому Оптимізатор запитів вважає, що найкращим вибором буде CIX + Sort.

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


4

Я вирішив трохи розібратися в цьому питанні, і я виявив кілька цікавих документів, які розповідають про те, як і коли використовувати, а може, краще, не (змушувати) використовувати некластеризований індекс.

Як підказано за коментарями Джона Ейсбренера , однією з найпопулярніших, навіть в інших блогах, є ця цікава стаття Кімберлі Л. Тріпп:

але це не єдине, якщо вам це цікаво, ви можете переглянути ці сторінки:

Як бачимо, всі вони рухаються навколо концепції пункту переломлення .

Цитується з статті KL Tripp

Що таке переломний момент?

Це момент, коли кількість повернутих рядків " вже не є достатньо вибірковою ". SQL Server вибирає НЕ використовувати некластеризований індекс для пошуку відповідних рядків даних і замість цього виконує сканування таблиці.

Коли SQL Server використовує некластеризований індекс на купі, в основному він отримує список покажчиків на сторінки базової таблиці. Потім він використовує ці покажчики для отримання рядків за допомогою ряду операцій під назвою Рядок пошуку ідентифікаторів рядків (RID). Це означає, що принаймні, воно буде використовувати стільки прочитаних сторінок, скільки кількість повернених рядків і, можливо, більше. Процес дещо схожий з кластерним індексом, як базова таблиця, з тим самим результатом: більше читає.

Але коли виникає ця переломна точка?

Звичайно, як і більшість речей у цьому житті, це залежить ...

Ніяк серйозно, це становить від 25% до 33% від кількості сторінок у таблиці, залежно від кількості рядків на сторінці. Але є кілька факторів, які слід враховувати:

Цитується з статті ITPRoToday

Інші фактори, що впливають на точку відкидання Хоча вартість пошуку RID є найважливішим фактором, який впливає на точку відхилення, є ряд інших факторів:

  • Фізичний введення / виведення набагато ефективніше при скануванні кластерного індексу. Кластерні дані індексу послідовно розміщуються на диску в порядку індексу. Отже, на диску дуже мало бічного руху голови, що покращує продуктивність вводу / виводу.
  • Коли двигун бази даних сканує кластерний індекс, він знає, що існує велика ймовірність того, що наступні кілька сторінок дискової доріжки все ще містять необхідні йому дані. Таким чином, він починає читати вперед в шматки 64 КБ замість звичайних сторінок 8 КБ. Це також призводить до швидшого вводу / виводу.

Тепер, якщо я знову виконую запити, використовуючи статистику IO:

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

Другий запит потребує більш логічних читань, ніж перший.

Чи слід уникати некластеризованого індексу?

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

Цитується з статті KL Tripp

Отже, що вам робити? Це залежить. Якщо ви добре знаєте свої дані і ви робите обширне тестування, ви можете розглянути можливість використання підказки (є кілька розумних речей, які ви можете програмно зробити в sps, я спробую скоро присвятити пост цьому). Однак набагато кращим вибором (якщо це взагалі можливо) є розгляд покриття (це справді мій головний пункт :). У моїх запитах покриття нереально, тому що мої запити хочуть, щоб усі стовпці (злий SELECT *), але якщо ваші запити вужчі, і вони є першочерговими, вам краще з індексом покриття (у багатьох випадках) над натяком, оскільки індекс, який охоплює запит, ніколи не підказує.

Це відповідь на головоломку на даний момент, але, безумовно, є набагато більше, чим зануритися. Точка перекидання може бути дуже хорошою справою - і вона зазвичай працює добре. Але, якщо ви виявите, що ви можете примусити індекс і отримати кращу ефективність, можливо, захочете провести кілька розслідувань і побачити, чи це все. Потім подумайте, наскільки вірогідний натяк на допомогу, і тепер ви знаєте, де можна зосередитися.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.