Очень хороший вопрос, поскольку это такая важная концепция. Это большая тема, и то, что я собираюсь показать вам, является упрощением, чтобы вы могли понять основные концепции.
Во-первых, когда вы видите таблицу кластеризованного индекса . На сервере 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 для операции поиска выглядит следующим образом:
- Выполните бинарный поиск по кластерному индексу, сравнивая искомое значение со значениями на промежуточном уровне.
- Вернуть совпадающие значения (помните, поскольку в кластеризованном индексе содержатся все данные, он может вернуть все столбцы из индекса, поскольку он является данными строки)
Так что это две операции. Однако, если мы выполнили следующий запрос:
SELECT * FROM Customer WHERE CustomerName ='John'
SQL теперь будет использовать некластеризованный индекс для CustomerName для поиска. Однако, поскольку это некластеризованный индекс, он не содержит все данные в строке.
Таким образом, SQL выполнит поиск на промежуточных уровнях, чтобы найти соответствующие записи, а затем выполнит поиск, используя возвращенные значения, чтобы выполнить другой поиск по кластерному индексу (или таблице), чтобы получить фактические данные. Это звучит странно, я знаю, но читаю, и все станет ясно.
Поскольку наш некластеризованный индекс содержит только поле CustomerName (значения индексированных полей, хранящиеся в промежуточных узлах) и указатель на данные, которые являются CustomerID, в индексе нет записи CustomerSurname. Имя CustomerSurname должно быть получено из кластерного индекса или таблицы.
При выполнении этого запроса я получаю следующий план выполнения:
На снимке экрана выше вы можете заметить две важные вещи
- SQL говорит, что у меня отсутствует индекс (текст зеленым). SQL предлагает создать индекс для CustomerName, который включает CustomerID и CustomerSurname.
- Вы также увидите, что 99% времени запроса тратится на поиск ключа по индексу первичного ключа / кластерному индексу.
Почему SQL снова предлагает индекс для CustomerName? Хорошо, поскольку индекс содержит только CustomerID и SQL CustomerName все еще должен найти CustomerSurname из таблицы / кластерных индексов.
Если бы мы создали индекс и включили столбец CustomerSurname в индекс SQL, он мог бы удовлетворить весь запрос, просто прочитав некластеризованный индекс. Вот почему SQL предлагает мне изменить свой некластеризованный индекс.
Здесь вы можете увидеть дополнительную операцию, которую должен выполнить SQL, чтобы получить столбец CustomerSurname из кластерного ключа.
Таким образом, количество операций выглядит следующим образом:
- Выполните бинарный поиск по некластеризованному индексу, сравнивая искомое значение со значениями на промежуточном уровне
- Для совпадающих узлов прочитайте узел конечного уровня, который будет содержать указатель для данных в кластеризованном индексе (узлы конечного уровня будут, кстати, содержать значения первичного ключа).
- Для каждого возвращенного значения выполните чтение кластерного индекса (таблицы), чтобы получить значения строк здесь, мы бы прочитали CustomerSurname.
- Вернуть совпадающие строки
Это 4 операции, чтобы получить значения. Вдвое больше операций, необходимых для чтения кластерного индекса. Показывает, что ваш кластеризованный индекс - ваш самый мощный индекс, поскольку он содержит все данные.
Так что просто уточнить один последний момент. Почему я говорю, что указатель в некластеризованном индексе является значением первичного ключа? Чтобы продемонстрировать, что узлы конечного уровня некластеризованного индекса содержат значение первичного ключа, я изменяю свой запрос на:
SELECT CustomerID
FROM Customer
WHERE CustomerName='Jane'
В этом запросе SQL может читать CustomerID из некластеризованного индекса. Для этого не нужно искать кластерный индекс. Это вы можете увидеть по плану выполнения, который выглядит следующим образом.
Обратите внимание на разницу между этим запросом и предыдущим запросом. Там нет поиска. SQL может найти все данные в некластеризованном индексе
Надеюсь, вы начнете понимать, что кластерный индекс - это таблица, а некластеризованные индексы не содержат всех данных. Индексирование ускорит выборку из-за того, что двоичный поиск может быть выполнен, но только кластерные индексы содержат все данные. Таким образом, поиск по некластеризованному индексу почти всегда приводит к загрузке значений из кластеризованного индекса. Эти дополнительные операции делают некластеризованные индексы менее эффективными, чем кластеризованный индекс.
Надеюсь, это прояснит ситуацию. Если что-то не имеет смысла, пожалуйста, оставьте комментарий, и я постараюсь уточнить. Здесь уже довольно поздно, и мой мозг чувствует себя крошечным. Время для красного быка.