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

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
Разница в производительности, когда вы делаете что? Какую работу вы хотите делать с этой таблицей? Нет единого решения, которое удовлетворяло бы все потребности
Ламак
2
Возможно, здесь есть какое-то осязаемое обсуждение stackoverflow.com/questions/91688/… stackoverflow.com/questions/5070529/… stackoverflow.com/questions/1251636/… Мы могли бы написать диссертацию о различиях между кластерными и некластеризованными индексами, но я не думаю, что мы скажет что-нибудь, что еще не доступно для вас, чтобы прочитать.
Аарон Бертран
4
Вы написали: «Это означает, что механизм запросов должен сделать дополнительный шаг, чтобы найти фактические данные». На самом деле, если все, что вам нужно, это столбцы, охватываемые индексом , вам не нужно предпринимать никаких дополнительных шагов после того, как вы найдете целевые строки в некластеризованном индексе. Только когда вам нужны столбцы, не включенные в некластеризованный индекс, SQL Server должен выполнить поиск закладок .
Ник Чаммас

Ответы:

43

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

Во-первых, когда вы видите таблицу кластеризованного индекса . На сервере SQL, если таблица не содержит кластеризованный индекс, это куча. Создание кластеризованного индекса в таблице фактически превращает таблицу в структуру типа b-дерева. Ваш кластерный индекс - это ваша таблица, она не отделена от таблицы

Вы когда-нибудь задумывались, почему у вас может быть только один кластерный индекс? Ну, если бы у нас было два кластеризованных индекса, нам понадобилось бы две копии таблицы. В конце концов, он содержит данные.

Я попытаюсь объяснить это на простом примере.

НОТА: Я создал таблицу в этом примере и заполнил ее более чем 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. Имя CustomerSurname должно быть получено из кластерного индекса или таблицы.

При выполнении этого запроса я получаю следующий план выполнения:

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

На снимке экрана выше вы можете заметить две важные вещи

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

Почему SQL снова предлагает индекс для CustomerName? Хорошо, поскольку индекс содержит только CustomerID и SQL CustomerName все еще должен найти CustomerSurname из таблицы / кластерных индексов.

Если бы мы создали индекс и включили столбец CustomerSurname в индекс SQL, он мог бы удовлетворить весь запрос, просто прочитав некластеризованный индекс. Вот почему SQL предлагает мне изменить свой некластеризованный индекс.

Здесь вы можете увидеть дополнительную операцию, которую должен выполнить SQL, чтобы получить столбец CustomerSurname из кластерного ключа.

Таким образом, количество операций выглядит следующим образом:

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

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

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

SELECT CustomerID
FROM Customer
WHERE CustomerName='Jane'

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

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

Обратите внимание на разницу между этим запросом и предыдущим запросом. Там нет поиска. SQL может найти все данные в некластеризованном индексе

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

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

Namphibian
источник
У меня вопрос. ПОЧЕМУ выполняется поиск по индексу по некластерному индексу в CustomerName для этого запроса SELECT * FROM Customer WHERE CustomerName = 'John'. Поскольку это некластеризованный индекс, имя клиента не будет отсортировано. Так что не следует выполнять сканирование индекса.
ckv
Кстати, отличный ответ, полностью понятный, за исключением вышеуказанного вопроса.
ckv
1
Индекс сортируется в порядке данных. Например, он будет отсортирован по имени клиента, поскольку это индексированное значение. Так что это отсортировано. Помните, что он все еще должен сканировать уровень листа или страниц.
Namphibian
9

«Это означает, что механизм запросов должен сделать дополнительный шаг, чтобы найти фактические данные».

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

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

Общее практическое правило, которое у меня есть, состоит в том, что таблица всегда имеет кластеризованный индекс (и обычно на идентификаторе или последовательном GUID), но некластеризованные индексы добавляются для повышения производительности. Но всегда есть исключения - у таблиц кучи есть место, у более широких кластерных индексов есть место. Кажется, что избыточные индексы, которые уже для размещения большего количества строк на странице, имеют место. и т. д.

И я не стал бы беспокоиться о допустимых пределах для различных индексов - это почти наверняка не войдет в игру во многих реальных примерах.

Кейд Ру
источник
2
+1 за there are always exceptions- слишком много людей опускают это и думают, что каждый кластерный индекс должен быть int identityнесмотря ни на что.
JNK