Po co używać klauzuli INCLUDE podczas tworzenia indeksu?

431

Podczas nauki do egzaminu 70-433 zauważyłem, że możesz utworzyć indeks obejmujący na jeden z dwóch poniższych sposobów.

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

- LUB -

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

Klauzula INCLUDE jest dla mnie nowa. Dlaczego miałbyś go używać i jakie wskazówki sugerowałbyś przy ustalaniu, czy utworzyć indeks obejmujący z klauzulą ​​INCLUDE, czy bez?

Cory
źródło

Odpowiedzi:

363

Jeśli kolumny nie ma w WHERE/JOIN/GROUP BY/ORDER BY, ale tylko na liście kolumn w SELECTklauzuli.

INCLUDEKlauzula dodaje dane na poziomie najniższym / liści, zamiast w drzewie indeksu. To zmniejsza indeks, ponieważ nie jest częścią drzewa

INCLUDE columnsnie są kluczowymi kolumnami w indeksie, więc nie są uporządkowane. Oznacza to, że nie jest to tak naprawdę przydatne w predykatach, sortowaniu itp., Jak wspomniałem powyżej. Jednak może być użyteczne, jeśli masz resztkowe wyszukiwanie w kilku wierszach od kluczowych kolumn

Kolejny artykuł MSDN z działającym przykładem

gbn
źródło
7
Czy byłaby to technika tworzenia tańszej wersji indeksu objętego gwarancją?
JMarsch,
3
@gbn, czy mógłbyś wyjaśnić to zdanie bardziej szczegółowo i wyjaśnić, dlaczego oznacza to, że klauzula include nie jest przydatna do sortowania itp.: „Klauzula INCLUDE dodaje dane na najniższym poziomie / liście, a nie w drzewie indeksu Dzięki temu indeks jest mniejszy, ponieważ nie jest częścią drzewa ”
Tola Odejayi
4
@JMarsch: przepraszam za późną odpowiedź, ale tak, to jest dokładnie to.
gbn
10
@Tola Odejayi: INCLUDE kolumny nie są kluczowymi kolumnami w indeksie, więc nie są uporządkowane. To sprawia, że zwykle nie są one przydatne do łączenia lub sortowania. A ponieważ nie są kluczowymi kolumnami, nie siedzą w całej strukturze B-drzewa jak kolumny kluczowe
gbn
4
Chociaż jest to najbardziej akceptowana odpowiedź, myślę, że potrzebne są dalsze wyjaśnienia, co jeśli w przypadku niektórych zapytań kolumna jest częścią, SELECTa dla niektórych nie? \
Chisko
215

Można użyć INCLUDE, aby dodać jedną lub więcej kolumn do poziomu liścia indeksu nieklastrowanego, jeśli dzięki temu można „zakryć” swoje zapytania.

Wyobraź sobie, że musisz zapytać o identyfikator pracownika, identyfikator działu i nazwisko.

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

Jeśli akurat masz indeks nieklastrowany na (EmployeeID, DepartmentID), po znalezieniu pracowników dla danego działu musisz teraz wykonać „przeglądanie zakładek”, aby uzyskać rzeczywisty pełny rekord pracownika, aby uzyskać kolumnę nazwiska . To może stać się dość drogie pod względem wydajności, jeśli znajdziesz wielu pracowników.

Jeśli włączyłeś to nazwisko do swojego indeksu:

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

wtedy wszystkie potrzebne informacje są dostępne na poziomie liści indeksu nieklastrowanego. Wystarczy poszukać w indeksie nieklastrowanym i znaleźć pracowników dla danego działu, masz wszystkie niezbędne informacje, a wyszukiwanie zakładek dla każdego pracownika w indeksie nie jest już konieczne -> oszczędzasz dużo czasu.

Oczywiście nie można uwzględnić każdej kolumny w każdym indeksie nieklastrowanym - ale jeśli masz zapytania, w których brakuje tylko jednej lub dwóch kolumn do „pokrycia” (i które często się przyzwyczajają), bardzo pomocne może być WŁĄCZENIE ich w odpowiedni indeks nieklastrowany.

marc_s
źródło
25
Czy na pewno użyjesz tego indeksu? Dlaczego identyfikator pracownika? Potrzebujesz tylko DepartmentID w kluczowych kolumnach? Zostałeś cytowany tutaj jako autorytatywny: stackoverflow.com/q/6187904/27535
gbn
3
Twoje wyjaśnienie jest dobre, ale tak naprawdę nie zgadza się z opisanym przypadkiem użycia. Kolumny z kluczami powinny znajdować się na filtrze lub JOINkluczach w zapytaniu, a INCLUDEmuszą to być dane, które pobierasz, ale nie sortujesz.
JNK
15
Przede wszystkim pracownika indeksu (EmployeeID, DepartmentID) nie będą używane do filtrowania DepartmentID = 5. Ponieważ jej Aby nie jest dopasowanie
AnandPhadke
29

W tej dyskusji brakuje ważnego punktu: nie chodzi o to, czy „kolumny niekluczowe” lepiej uwzględnić jako kolumny indeksowe lub jako kolumny włączone .

Pytanie brzmi, jak drogie jest użycie mechanizmu włączania do uwzględnienia kolumn, które tak naprawdę niepotrzebne w indeksie ? (zazwyczaj nie jest częścią klauzul gdzie, ale często jest włączana do selekcji). Zatem twoim dylematem jest zawsze:

  1. Zastosowanie indeksu na ID1, ID2 ... IDN sam lub
  2. Użyj indeksu na id1, id2 ... idN plus obejmują col1, col2 ... colN

Gdzie: ID1, ID2 ... IDN są często stosowane w kolumnach ograniczeń i col1, col2 ... Cöln kolumny są często wybrane, ale zwykle nie stosuje się w ograniczeniach

(Opcja włączenia wszystkich tych kolumn jako części klawisza indeksu jest po prostu zawsze głupia (chyba że są one również używane w ograniczeniach) - ponieważ utrzymanie zawsze byłoby droższe, ponieważ indeks musi być aktualizowany i sortowany, nawet gdy „klucze” nie uległy zmianie).

Więc użyj opcji 1 lub 2?

Odpowiedź: Jeśli tabela jest rzadko aktualizowana - głównie wstawiony / usunięty z - to jest stosunkowo niedrogie w użyciu include-mechanizm zawierać kilka „gorących kolumny” (które są często używane w wybiera - ale nie często używany w sprawie ograniczeń), ponieważ wstawianie / usuwanie wymaga i tak aktualizacji / sortowania indeksu, a zatem niewielkie dodatkowe obciążenie związane jest z przechowywaniem kilku dodatkowych kolumn podczas już aktualizacji indeksu. Narzut to dodatkowa pamięć i procesor używane do przechowywania nadmiarowych informacji w indeksie.

Jeśli kolumny, które chcesz dodać jako dołączone, są często aktualizowane (bez aktualizacji klucza indeksu -kolumny) - lub - jeśli jest ich tak wiele, że indeks zbliża się do kopii tabeli - skorzystaj z opcji 1 Sugerowałbym! Także jeśli dodanie niektórych kolumn uwzględnienia nie spowoduje różnicy w wydajności - możesz pominąć pomysł ich dodania :) Sprawdź, czy są one przydatne!

Ważna może być również średnia liczba wierszy dla tych samych wartości w kluczach (id1, id2 ... idN).

Zauważ, że jeśli kolumna - która jest dodawana jako dołączona kolumna indeksu - jest używana w ograniczeniu : Tak długo, jak indeks może być używany jako taki (w oparciu o ograniczenie przeciwko kluczowi indeksu -kolumny) - wtedy SQL Server jest zgodny ograniczenie kolumny względem indeksu (wartości typu liść-węzeł) zamiast omijać kosztowną drogę wokół samej tabeli.

Fredrik Solhaug
źródło
18

Podstawowe kolumny indeksu są sortowane, ale uwzględnione kolumny nie są sortowane. Oszczędza to zasoby w utrzymywaniu indeksu, a jednocześnie umożliwia podawanie danych w dołączonych kolumnach na potrzeby zapytania. Tak więc, jeśli chcesz uwzględnić zapytania, możesz umieścić kryteria wyszukiwania, aby zlokalizować wiersze w posortowanych kolumnach indeksu, a następnie „uwzględnić” dodatkowe, nieposortowane kolumny z danymi nieprzeszukiwanymi. To zdecydowanie pomaga w zmniejszeniu ilości sortowania i fragmentacji w utrzymywaniu indeksu.

onupdatecascade
źródło
7

Powody, dla których (w tym dane na poziomie liści indeksu) zostały dobrze wyjaśnione. Powodem, dla którego podajesz dwa drżenia na ten temat, jest to, że po uruchomieniu zapytania, jeśli nie masz dodatkowych kolumn (nowa funkcja w SQL 2005), SQL Server musi przejść do indeksu klastrowego, aby uzyskać dodatkowe kolumny co zajmuje więcej czasu i zwiększa obciążenie usługi SQL Server, dysków i pamięci (konkretnie pamięci podręcznej bufora), gdy nowe strony danych są ładowane do pamięci, potencjalnie wypychając inne, często potrzebne dane, z pamięci podręcznej bufora.

mrdenny
źródło
czy istnieje sposób, aby udowodnić, że faktycznie zużywa mniej pamięci? tego też bym się spodziewał, ale robię się trochę
niezrozumiały
Biorąc pod uwagę, że musisz załadować stronę ze sterty lub indeksu klastrowego do pamięci, a także stronę indeksu, co oznacza, że ​​umieszczasz zduplikowane dane w pamięci, matematyka staje się dość prosta. Jeśli chodzi o sposób na konkretny pomiar, nie, nie ma.
mrdenny,
5

Dodatkowym zagadnieniem, którego nie widziałem w odpowiedziach już podanych, jest to, że dołączone kolumny mogą należeć do typów danych, które nie są dozwolone jako kolumny klucza indeksu, takie jak varchar (max).

Pozwala to na włączenie takich kolumn do indeksu obejmującego. Niedawno musiałem to zrobić, aby dostarczyć zapytanie generowane przez nHibernate, które zawierało wiele kolumn w SELECT, z przydatnym indeksem.

Robin Hames
źródło
3

Jednym z powodów, aby preferować INCLUDEkolumny klucza, jeśli nie potrzebujesz tej kolumny w kluczu, jest dokumentacja. To sprawia, że ​​ewolucja indeksów będzie znacznie łatwiejsza w przyszłości.

Biorąc pod uwagę twój przykład:

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

Ten indeks jest najlepszy, jeśli zapytanie wygląda następująco:

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

Oczywiście nie powinieneś wstawiać kolumn, INCLUDEjeśli możesz uzyskać dodatkową korzyść z posiadania ich w kluczowej części. Oba poniższe zapytania faktycznie wolą col2kolumnę w kluczu indeksu.

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

Załóżmy, że tak nie jest i mamy to col2w INCLUDEklauzuli, ponieważ po prostu nie ma korzyści z posiadania go w części drzewa indeksu.

Kilka lat do przodu.

Musisz dostroić to zapytanie:

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

Aby zoptymalizować to zapytanie, świetny byłby następujący indeks:

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

Jeśli sprawdzisz, jakie indeksy masz już w tej tabeli, twój poprzedni indeks może nadal tam być:

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

Teraz wiesz o tym Col2i Col3nie są częścią drzewa indeksów, a zatem nie są używane do zawężania zakresu indeksu odczytu ani do porządkowania wierszy. Można raczej bezpiecznie dodać another_columnna końcu kluczowej części indeksu (po col1). Ryzyko zniszczenia czegokolwiek jest niewielkie:

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

Indeks ten będzie większy, co nadal wiąże się z pewnym ryzykiem, ale ogólnie lepiej jest rozszerzyć istniejące indeksy niż wprowadzać nowe.

Jeśli miałbyś indeks bez INCLUDE, nie wiedziałbyś, jakie zapytania przerwałbyś, dodając another_colzaraz potem Col1.

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

Co się stanie, jeśli dodasz another_colpomiędzy Col1i Col2? Czy ucierpią inne zapytania?

Istnieją inne „zalety” w INCLUDEporównaniu z kluczowymi kolumnami, jeśli dodasz te kolumny tylko po to, aby uniknąć pobierania ich z tabeli . Uważam jednak, że najważniejszy jest aspekt dokumentacji.

Odpowiedzieć na Twoje pytanie:

jakie wytyczne zasugerowałbyś przy ustalaniu, czy utworzyć indeks obejmujący z klauzulą ​​INCLUDE, czy bez?

Jeśli dodasz kolumnę do indeksu wyłącznie w celu udostępnienia tej kolumny w indeksie bez odwiedzania tabeli, umieść ją w INCLUDEklauzuli.

Jeśli dodanie kolumny do klucza indeksu przynosi dodatkowe korzyści (np. Dla order bylub ponieważ może zawęzić zakres odczytu indeksu), dodaj ją do klucza.

Dłuższą dyskusję na ten temat możesz przeczytać tutaj:

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

Markus Winand
źródło
2

W definicji indeksu istnieje ograniczenie całkowitego rozmiaru wszystkich kolumn. Mimo to nigdy nie musiałem tworzyć tak szerokiego indeksu. Dla mnie większą zaletą jest to, że możesz pokryć więcej zapytań za pomocą jednego indeksu zawierającego kolumny, ponieważ nie trzeba ich definiować w określonej kolejności. Pomyśl o tym jak o indeksie w ramach indeksu. Przykładem może być StoreID (gdzie StoreID ma niską selektywność, co oznacza, że ​​każdy sklep jest powiązany z wieloma klientami), a następnie dane demograficzne klientów (LastName, FirstName, DOB): Jeśli wstawisz te kolumny w tej kolejności (StoreID, LastName , FirstName, DOB), możesz tylko efektywnie wyszukiwać klientów, dla których znasz StoreID i LastName.

Z drugiej strony, zdefiniowanie indeksu na StoreID i uwzględnienie kolumn LastName, FirstName, DOB pozwoliłoby w zasadzie na wykonanie dwóch predykatów wyszukiwania na StoreID, a następnie wyszukanie predykatu na dowolnej z zawartych kolumn. Pozwoliłoby to objąć wszystkie możliwe permutacje wyszukiwania, o ile zaczynają się od StoreID.

mEmENT0m0RI
źródło