Різниця продуктивності між індексом кластеру та не кластером


22

Я читав Clusteredі Non Clustered Indexes.

Clustered Index- Він містить Сторінки даних. Це означає, що повна інформація про рядки буде присутня у стовпці з індексом кластера.

Non Clustered Index- Він містить лише інформацію про локатор рядків у формі стовпця «Кластерний індекс» (якщо є доступний) або «Індикатор файлу» + «Номер сторінки + загальний рядок на сторінці». Це означає, що система пошуку запитів повинна зробити додатковий крок, щоб знайти фактичні дані.

Запит - Як я можу перевірити різницю продуктивності за допомогою практичного прикладу, оскільки ми знаємо, що таблиця може мати лише одне Clustered Indexі надає sortingна Clustered Index Columnта Non Clustered Indexне надає sortingта може підтримувати 999 Non Clustered Indexesв SQL Server 2008і 249 дюйма SQL Server 2005.


2
Різниця в продуктивності, коли ви робите що ?, яку роботу ви хочете робити з цим столом ?, не існує жодного рішення, яке підходить для кожної потреби
Lamak,

2
Можливо, тут якась відчутна дискусія. stackoverflow.com/questions/91688/… stackoverflow.com/questions/5070529/… stackoverflow.com/questions/1251636/… Ми могли б написати дисертацію про відмінності між кластерними та некластеризованими індексами, але я не думаю, що ми сказав би все, що ще не доступне для читання.
Аарон Бертран

4
Ви писали: "Це означає, що система пошуку запитів повинна зробити додатковий крок, щоб знайти фактичні дані". Насправді, якщо все, що вам потрібно, - це стовпці, охоплені індексом , вам не потрібно робити жодних додаткових кроків після того, як ви знайдете цільові рядки в некластеризованому індексі. Тільки коли вам потрібні стовпці, не охоплені некластеризованим індексом, SQL Server повинен здійснити пошук закладок .
Нік Чаммас

Відповіді:


43

Дуже добре запитання, оскільки це така важлива концепція. Хоча це велика тема, і те, що я хочу вам показати, - це спрощення, щоб ви могли зрозуміти базові поняття.

По-перше, коли ви бачите кластерну таблицю роздумів індексу . На сервері SQL, якщо таблиця не містить кластерного індексу, це купа. Створення кластерного індексу на столі фактично перетворює таблицю в структуру типу b-tree. Ваш кластерний індекс - це ваша таблиця, вона не відокремлена від таблиці

Ніколи не замислювалися, чому у вас може бути лише один кластерний індекс? Добре, якби у нас було два кластерні індекси, нам знадобилися б дві копії таблиці. Зрештою, він містить дані.

Я спробую пояснити це на простому прикладі.

ПРИМІТКА: Я створив таблицю в цьому прикладі і заповнив її понад 3 мільйонами випадкових записів. Потім запустили фактичні запити та вставили сюди плани виконання.

Що вам дійсно потрібно зрозуміти, це O-позначення або операційна ефективність . Припустимо, у вас є наступна таблиця.

CREATE TABLE [dbo].[Customer](
[CustomerID] [int] IDENTITY(1,1) NOT NULL,
[CustomerName] [varchar](100) NOT NULL,
[CustomerSurname] [varchar](100) NOT NULL,
CONSTRAINT [PK_Customer] PRIMARY KEY CLUSTERED 
(
[CustomerID] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF
  , IGNORE_DUP_KEY = OFF,ALLOW_ROW_LOCKS  = ON
  , ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY]

Отже, у нас є основна таблиця з кластерним ключем на CustomerID (Первинний ключ кластеризований за замовчуванням). Таким чином таблиця розташовується / упорядковується на основі первинного ключа CustomerID. Проміжні рівні містять значення CustomerID. Сторінки даних будуть містити весь рядок, таким чином, це рядок таблиці.

Ми також створимо некластеризований індекс у полі CustomerName. Наступний код зробить це.

CREATE NONCLUSTERED INDEX [ix_Customer_CustomerName] ON [dbo].[Customer] 
 (
[CustomerName] ASC
 )WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF
  , SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF
  , DROP_EXISTING = OFF, ONLINE = OFF
  , ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]

Отже, в цьому індексі ви знайдете на сторінках даних / вузлах рівня листів вказівник на проміжні рівні в кластерному індексі. Індекс розташовується / упорядковується навколо поля CustomerName. Таким чином, проміжний рівень містить значення CustomerName, а рівень аркуша міститиме вказівник (ці значення вказівника фактично є значеннями первинного ключа або стовпцем CustomerID).

Так, якщо ми виконаємо такий запит:

SELECT * FROM Customer WHERE CustomerID = 1 

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

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

Отже, кількість операцій або O-позначення для операції пошуку є такою:

  1. Здійснюйте двійковий пошук по кластерному індексу, порівнюючи шукане значення зі значеннями на проміжному рівні.
  2. Поверніть значення, які відповідають (пам’ятайте, оскільки кластерний індекс містить усі дані, він може повернути всі стовпці з індексу, оскільки це дані рядків)

Отже, це дві операції. Однак якщо ми виконали такий запит:

SELECT * FROM Customer WHERE CustomerName ='John'

Тепер SQL використовуватиме некластеризований індекс у CustomerName для пошуку. Однак, оскільки це некластеризований індекс, він не містить усіх даних у рядку.

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

Оскільки наш некластеризований індекс містить лише поле CustomerName (значення індексованого поля, що зберігаються у проміжних вузлах) та вказівник на дані, які є CustomerID, індекс не має запису імені CustomerSurname. Ім'я клієнта має бути отримано з кластерного індексу або таблиці.

Під час запуску цього запиту я отримую такий план виконання:

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

На екрані, знятому вище, ви можете помітити дві важливі речі

  1. SQL говорить, що у мене відсутній індекс (текст зеленим кольором). SQL пропонує мені створити індекс на CustomerName, який включає CustomerID та CustomerSurname.
  2. Ви також побачите, що 99% часу запиту витрачається на пошук ключа на індекс первинного ключа / кластерний індекс.

Чому SQL знову пропонує індекс на CustomerName? Отже, оскільки індекс містить лише CustomerID, а SQL CustomerName все ще повинен знайти ім'я CustomerSurname з таблиці / кластерних індексів.

Якби ми створили індекс і включили стовпець CustomerSurname до індексу SQL, можна було б задовольнити весь запит, просто прочитавши некластеризований індекс. Ось чому SQL пропонує мені змінити некластеризований індекс.

Тут ви можете побачити додаткову операцію, яку повинен виконати SQL, щоб отримати стовпець CustomerSurname з кластерного ключа

Таким чином, кількість операцій така:

  1. Здійснюйте двійковий пошук по некластеризованому індексу, порівнюючи шукане значення зі значеннями на проміжному рівні
  2. Для вузлів, які відповідають прочитаному вузлу рівня аркуша, який буде містити вказівник для даних кластеризованого індексу (вузли рівня листів, до речі, містять значення первинного ключа).
  3. Для кожного повернутого значення зробимо читання на кластерному індексі (таблиці), щоб отримати значення рядків тут, ми прочитали б ім'я клієнта.
  4. Повернути відповідні рядки

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

Тому просто для уточнення останнього моменту. Чому я кажу, що вказівник у некластеризованому індексі є значенням основного ключа? Добре, щоб продемонструвати, що вузли рівня листів некластеризованого індексу містять значення первинного ключа, я змінюю свій запит на:

SELECT CustomerID
FROM Customer
WHERE CustomerName='Jane'

У цьому запиті SQL може прочитати CustomerID з некластеризованого індексу. Тут не потрібно робити пошук на кластерному індексі. Це ви можете бачити за планом виконання, який виглядає приблизно так.

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

Зауважте різницю між цим запитом та попереднім запитом. Немає пошуку. SQL може знайти всі дані в некластерному індексі

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

Сподіваюсь, це все прояснить. Якщо нічого не має сенсу, будь ласка, опублікуйте коментар, і я спробую уточнити. Тут досить пізно, і мій мозок відчуває плаксивість. Час для червоного бика.


У мене є питання. Чому пошук шукає індекс по некластеризованому індексу на CustomerName для цього запиту SELECT * FROM Customer WHERE CustomerName = 'John'. Оскільки це некластеризований індекс, ім'я клієнта не буде відсортовано. Тому не слід робити сканування індексів.
ckv

До речі, чудова відповідь повністю зрозуміла, за винятком вищезазначеного питання.
ckv

1
Індекс відсортований у порядку даних. Наприклад, він буде відсортований за назвою Клієнта, оскільки це індексоване значення. Так воно сортується. Пам'ятайте, що досі потрібно сканувати рівень аркушів або сторінок.
Namphibian

9

"Це означає, що двигун запитів повинен зробити додатковий крок, щоб знайти фактичні дані."

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

Отже, остаточна відповідь - Залежить (набагато більше інформації, ніж ви дійсно можете охопити в одному запитанні) - вам потрібно зрозуміти всі можливості індексів, і план виконання даного запиту може відрізнятися від ваших очікувань.

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

І я б не переймався обмеженнями для різних дозволених індексів - це майже напевно не буде грати у багатьох прикладах реального світу.


2
+1 для there are always exceptions- занадто багато людей пропускають це і вважають, що кожен кластерний індекс повинен бути int identityнезалежним.
JNK
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.