Зачем использовать предложение INCLUDE при создании индекса?

432

Во время подготовки к экзамену 70-433 я заметил, что вы можете создать индекс покрытия одним из следующих двух способов.

CREATE INDEX idx1 ON MyTable (Col1, Col2, Col3)

-- ИЛИ --

CREATE INDEX idx1 ON MyTable (Col1) INCLUDE (Col2, Col3)

Предложение INCLUDE является новым для меня. Зачем вам его использовать и какие рекомендации вы бы предложили при определении, создавать ли индекс покрытия с предложением INCLUDE или без него?

Cory
источник

Ответы:

364

Если столбца нет в WHERE/JOIN/GROUP BY/ORDER BY, а только в списке столбцов в SELECTпредложении.

Предложение INCLUDEдобавляет данные на самом нижнем / листовом уровне, а не в дереве индексов. Это делает индекс меньше, потому что он не является частью дерева

INCLUDE columnsне являются ключевыми столбцами в индексе, поэтому они не упорядочены. Это означает, что это не очень полезно для предикатов, сортировки и т. Д., Как я упоминал выше. Тем не менее, это может быть полезно, если у вас есть остаточный поиск в нескольких строках из ключевых столбцов

Еще одна статья MSDN с работающим примером

ГБН
источник
7
Итак, это будет техника для создания менее дорогой версии покрытого индекса?
JMarsch
3
@gbn, не могли бы вы объяснить это предложение более подробно и объяснить, почему это означает, что предложение include бесполезно для сортировки и т. д .: «Предложение INCLUDE добавляет данные на самом низком / листовом уровне, а не в дереве индекса . Это делает индекс меньше, потому что он не является частью дерева "
Тола Одежаи
4
@JMarsch: извините за поздний ответ, но да, это именно то, что есть.
ГБН
10
@Tola Odejayi: INCLUDE столбцы не являются ключевыми столбцами в индексе, поэтому они не упорядочены. Это делает их, как правило, бесполезными для соединения или сортировки. И поскольку они не являются ключевыми столбцами, они не располагаются во всей структуре B-дерева, как ключевые столбцы
gbn
4
Хотя это наиболее приемлемый ответ, я думаю, что необходимы дальнейшие пояснения, а что если для некоторых запросов столбец является частью, SELECTа для некоторых нет? \
Чиско
215

Вы бы использовали INCLUDE, чтобы добавить один или несколько столбцов к конечному уровню некластеризованного индекса, если, сделав это, вы можете «покрыть» ваши запросы.

Представьте, что вам нужно запросить идентификатор сотрудника, идентификатор отдела и фамилию.

SELECT EmployeeID, DepartmentID, LastName
FROM Employee
WHERE DepartmentID = 5

Если у вас есть некластеризованный индекс (EmployeeID, DepartmentID), как только вы найдете сотрудников для определенного отдела, вам теперь нужно выполнить «поиск по закладкам», чтобы получить фактическую полную запись о сотруднике, просто чтобы получить столбец фамилии , Это может быть довольно дорого с точки зрения производительности, если вы найдете много сотрудников.

Если вы включили эту фамилию в свой индекс:

CREATE NONCLUSTERED INDEX NC_EmpDep 
  ON Employee(EmployeeID, DepartmentID)
  INCLUDE (Lastname)

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

Очевидно, что вы не можете включить каждый столбец в каждый некластеризованный индекс - но если у вас есть запросы, в которых отсутствует только один или два столбца, которые нужно «покрыть» (и которые часто используются), это может быть очень полезно, чтобы ВКЛЮЧИТЬ эти в подходящий некластерный индекс.

marc_s
источник
25
Вы уверены, что будете использовать этот индекс? Почему EmployeeID? Вам нужен только DepartmentID в ключевых столбцах? Вас цитируют здесь как автора: stackoverflow.com/q/6187904/27535
gbn
3
Ваше объяснение хорошо, но на самом деле не соответствует описанному вами сценарию использования. Ключевой столбец (столбцы) должен находиться в фильтре или JOINключах в запросе, а столбцы должны быть INCLUDEданными, которые вы извлекаете, но не сортируете.
JNK
15
Прежде всего, индекс Employee (EmployeeID, DepartmentID) не будет использоваться для фильтрации DepartmentID = 5. Поскольку его порядок не совпадает
AnandPhadke
29

В этом обсуждении упущен важный момент: вопрос не в том, лучше ли включать «не ключевые столбцы» в качестве столбцов индекса или включенных столбцов.

Вопрос в том, насколько дорого использовать механизм включения для включения столбцов, которые на самом деле не нужны в индексе ? (обычно не входит в предложения where, но часто включается в выборки). Так что ваша дилемма всегда:

  1. Используйте индекс id1, id2 ... IDN в одиночку или
  2. Использовать индекс для id1, id2 ... idN плюс включить col1, col2 ... colN

Где: id1, id2 ... idN - столбцы, часто используемые в ограничениях, а col1, col2 ... colN - часто выбираемые столбцы, но обычно они не используются в ограничениях.

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

Так использовать вариант 1 или 2?

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

Если столбцы, которые вы хотите добавить как включенные столбцы, часто обновляются (без обновления ключа ключа индекса) - или - если их так много, что индекс становится близким к копии вашей таблицы - используйте опцию 1 Я бы предложил! Кроме того, если добавление определенных столбцов include-столбцов не оказывает никакого влияния на производительность - вы можете пропустить идею их добавления :) Убедитесь, что они полезны!

Среднее число строк на одно и то же значение в ключах (id1, id2 ... idN) также может иметь некоторое значение.

Обратите внимание , что если столбец - который добавляется как включен -column индекса - используется в ограничении : До тех пор , как индекс , как таковой , может быть использован ( на основе ограничений в отношении index- ключевых -columns) - то SQL - сервер является соответствие ограничение столбца по отношению к индексу (leaf-node-values) вместо дорогостоящего обхода самой таблицы.

Фредрик Солхауг
источник
18

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

onupdatecascade
источник
7

Причины (включая данные на уровне листа индекса) были хорошо объяснены. Причиной, по которой вы получаете два эффекта, является то, что при выполнении запроса, если у вас нет дополнительных столбцов (новая функция в SQL 2005), SQL Server должен перейти к кластерному индексу, чтобы получить дополнительные столбцы Это занимает больше времени и увеличивает нагрузку на службу SQL Server, диски и память (в частности, буферный кэш), так как новые страницы данных загружаются в память, что потенциально выталкивает другие более часто необходимые данные из буферного кеша.

mrdenny
источник
Есть ли способ доказать, что он на самом деле использует меньше памяти? это то, чего я тоже ожидал, но я получаю некоторую статику об этом на работе
Asken
Учитывая, что вам нужно загрузить страницу из кучи или кластерного индекса в память, а также страницу индекса, что означает, что вы помещаете в память дублирующиеся данные, математика становится довольно простой. Что касается способа конкретно его измерить, то нет, нет.
Мрденный
5

Дополнительное соображение, которое я не видел в уже приведенных ответах, заключается в том, что включенные столбцы могут относиться к типам данных, которые не допускаются в качестве столбцов ключа индекса, например varchar (max).

Это позволяет включать такие столбцы в индекс покрытия. Недавно мне пришлось сделать это, чтобы предоставить сгенерированный запрос nHibernate, в котором было много столбцов в SELECT, с полезным индексом.

Робин Хеймс
источник
3

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

Учитывая ваш пример:

CREATE INDEX idx1 ON MyTable (Col1) INCLUDE (Col2, Col3)

Этот индекс лучше всего подходит, если ваш запрос выглядит так:

SELECT col2, col3
  FROM MyTable
 WHERE col1 = ...

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

SELECT col2, col3
  FROM MyTable
 WHERE col1 = ...
   AND col2 = ...
SELECT TOP 1 col2, col3
  FROM MyTable
 WHERE col1 = ...
 ORDER BY col2

Давайте предположим, что это не так, и мы имеем col2в INCLUDEпредложении, потому что нет никакой выгоды иметь его в древовидной части индекса.

Перемотка вперед на несколько лет.

Вам нужно настроить этот запрос:

SELECT TOP 1 col2
  FROM MyTable
 WHERE col1 = ...
 ORDER BY another_col

Чтобы оптимизировать этот запрос, подойдет следующий индекс:

CREATE INDEX idx1 ON MyTable (Col1, another_col) INCLUDE (Col2)

Если вы проверите, какие индексы у вас уже есть в этой таблице, ваш предыдущий индекс все еще может быть там:

CREATE INDEX idx1 ON MyTable (Col1) INCLUDE (Col2, Col3)

Теперь вы знаете , что Col2и Col3не являются частью индексного дерева и, таким образом , не используются , чтобы сузить диапазон индексов для чтения , ни для упорядочения строк. Достаточно безопасно добавить another_columnв конец ключевой части индекса (после col1). Существует небольшой риск сломать что-либо:

DROP INDEX idx1 ON MyTable;
CREATE INDEX idx1 ON MyTable (Col1, another_col) INCLUDE (Col2, Col3);

Этот индекс станет больше, что по-прежнему сопряжено с некоторыми рисками, но, как правило, лучше расширять существующие индексы, чем вводить новые.

Если бы у вас был индекс без INCLUDE, вы не могли бы знать, какие запросы вы нарушите, добавив another_colсразу после Col1.

CREATE INDEX idx1 ON MyTable (Col1, Col2, Col3)

Что произойдет, если вы добавите another_colмежду Col1и Col2? Будут ли страдать другие запросы?

Существуют и другие «преимущества» INCLUDEстолбцов против ключа, если вы добавляете эти столбцы просто для того, чтобы избежать их извлечения из таблицы . Тем не менее, я считаю аспект документации самым важным.

Чтобы ответить на ваш вопрос:

Какие рекомендации вы бы предложили при определении, создавать ли индекс покрытия с предложением INCLUDE или без него?

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

Если добавление столбца к ключу индекса приносит дополнительные преимущества (например, для order byили потому, что оно может сузить диапазон индекса чтения), добавьте его к ключу.

Вы можете прочитать более подробное обсуждение этого здесь:

https://use-the-index-luke.com/blog/2019-04/include-columns-in-btree-indexes

Маркус Винанд
источник
2

Существует ограничение на общий размер всех столбцов, встроенных в определение индекса. Тем не менее, мне никогда не приходилось создавать такой широкий индекс. Для меня большее преимущество заключается в том, что вы можете охватить больше запросов одним индексом, который включает столбцы, поскольку их не нужно определять в каком-то определенном порядке. Думайте о том, как индекс в индексе. Одним из примеров может быть StoreID (где StoreID - низкая селективность, что означает, что каждый магазин связан с большим количеством клиентов), а затем демографические данные клиентов (LastName, FirstName, DOB): если вы просто вставляете эти столбцы в этом порядке (StoreID, LastName , FirstName, DOB), вы можете эффективно искать только тех клиентов, для которых вы знаете StoreID и LastName.

С другой стороны, определение индекса для StoreID и включение столбцов LastName, FirstName, DOB, по сути, позволит вам выполнить два предиката индекса поиска для StoreID, а затем искать предикат для любого из включенных столбцов. Это позволит вам охватить все возможные варианты поиска, если он начинается с StoreID.

mEmENT0m0RI
источник