Dlaczego moja klauzula WHERE korzysta z kolumny „uwzględnione”?

12

Zgodnie z tą odpowiedzią , o ile indeks nie zostanie zbudowany na kolumnach służących do ograniczenia, zapytanie nie skorzysta z indeksu.

Mam tę definicję:

CREATE TABLE [dbo].[JobItems] (
    [ItemId]             UNIQUEIDENTIFIER NOT NULL,
    [ItemState]          INT              NOT NULL,
    [ItemPriority]       INT NOT NULL,
    [CreationTime]       DATETIME         NULL DEFAULT GETUTCDATE(),
    [LastAccessTime]     DATETIME         NULL DEFAULT GETUTCDATE(),
     -- other columns
 );

 CREATE UNIQUE CLUSTERED INDEX [JobItemsIndex]
    ON [dbo].[JobItems]([ItemId] ASC);
 GO

CREATE INDEX [GetItemToProcessIndex]
    ON [dbo].[JobItems]([ItemState], [ItemPriority], [CreationTime])
    INCLUDE (LastAccessTime);
GO

i to zapytanie:

UPDATE TOP (150) JobItems 
SET ItemState = 17 
WHERE 
    ItemState IN (3, 9, 10)
    AND LastAccessTime < DATEADD (day, -2, GETUTCDATE()) 
    AND CreationTime < DATEADD (day, -2, GETUTCDATE());

Przejrzałem aktualny plan i jest tylko jedno wyszukiwanie indeksu z predykatem dokładnie tak, jak w przypadku WHERE- żadnych dodatkowych „wyszukiwań zakładek” do pobrania, LastAccessTimenawet jeśli to ostatnie jest „uwzględnione” w indeksie, a nie jego części.

Wydaje mi się, że takie zachowanie jest sprzeczne z regułą, że kolumna musi być częścią indeksu, a nie tylko „uwzględniona”.

Czy zachowanie, które obserwuję, jest właściwe? Skąd mam wiedzieć z góry, czy moje WHEREkorzyści z dołączonej kolumny lub czy kolumna jest częścią indeksu?

sharptooth
źródło
Może nadal szukać w oparciu o ItemStatewartość, jednak szukanie nie będzie tak skuteczne, jak gdyby indeks miał następującą strukturę(ItemState, CreationTime, LastAccessTime)
Mark Sinkinson
1
@MarkSinkinson or just(ItemState, CreationTime) INCLUDE (LastAccessTime)
ypercubeᵀᴹ
@sharptooth połączona odpowiedź nie mówi, że („chyba że indeks jest zbudowany na kolumnach, które służą do ograniczenia zapytania, nie skorzysta z indeksu”). Mówi, że indeks na (a,b)nie jest najlepszy dla zapytania, SELECT a FROM t WHERE b=5;a indeks na (b) INCLUDE (a)jest znacznie lepszy.
ypercubeᵀᴹ

Odpowiedzi:

9

Twój Predykat różni się od Twojego Predykatu wyszukiwania.

Predykat wyszukiwania służy do wyszukiwania uporządkowanych danych w indeksie. W tym przypadku będzie to trzy wyszukiwania, po jednym dla każdego interesującego Cię elementu ItemState. Poza tym dane są w kolejności ItemPriority, więc nie można wykonać żadnej kolejnej operacji „Seek”.

Ale zanim dane zostaną zwrócone, sprawdza każdy wiersz za pomocą predykatu, który nazywam predykatem rezydualnym. Odbywa się to na podstawie wyników wyszukiwania.

Każda dołączona kolumna nie jest częścią uporządkowanych danych, ale można jej użyć do spełnienia predykatu rezydualnego, bez konieczności dodatkowego wyszukiwania.

Możesz zobaczyć materiał, który napisałem na ten temat wokół Sargability. Sprawdź sesję w szczególności w SQLBits, pod adresem http://bit.ly/Sargability

Edycja: Aby lepiej pokazać wpływ Residuals, uruchom zapytanie za pomocą nieudokumentowanej OPTION (QUERYTRACEON 9130), która rozdzieli Residual na osobny operator filtru (który jest tak naprawdę wcześniejszą wersją planu, zanim resztka zostanie przeniesiona do operatora Seek). Wyraźnie pokazuje wpływ nieskutecznego wyszukiwania, przez liczbę wierszy przekazanych do filtra.

Warto również zauważyć, że ze względu na klauzulę IN na ItemState dane przekazywane w lewo są w rzeczywistości w porządku ItemState, a nie w porządku ItemPriority. Złożony indeks na ItemState, po którym następuje jedna z dat (np. (ItemState, LastAccessTime)), może być wykorzystany do trzech wyszukiwań (zauważ, że Predykat wyszukiwania pokazuje trzy wyszukiwania w ramach jednego operatora wyszukiwania), każdy na dwóch poziomach, generując dane, które są wciąż w porządku ItemState (np. ItemState = 3 i LastAccessTime mniej niż coś, następnie ItemState = 9 i LastAccessTime mniej niż coś, a następnie ItemState = 10 i LastAccessTime mniej niż coś).

Indeks na (ItemState, LastAccesTime, CreationTime) nie byłby bardziej użyteczny niż jeden na (ItemState, LastAccessTime), ponieważ poziom CreationTime jest użyteczny tylko wtedy, gdy Twoje wyszukiwanie jest dla określonej kombinacji ItemState i LastAccessTime, a nie zakresu. Jak w przypadku, gdy książka telefoniczna nie jest w porządku Imię, jeśli interesują Cię Nazwiska zaczynające się na F.

Jeśli chcesz indeks złożony, ale nigdy nie będziesz w stanie używać późniejszych kolumn w Seek Predicates ze względu na sposób korzystania z wcześniejszych kolumn, możesz równie dobrze mieć je jako uwzględnione kolumny, w których zajmują mniej miejsca w indeks (ponieważ są one przechowywane tylko na poziomie liścia indeksu, a nie na wyższych poziomach), ale nadal mogą unikać wyszukiwań i być wykorzystywane w predykatach rezydualnych.

Zgodnie z terminem Predykat rezydualny - to mój własny termin dla tej właściwości Poszukiwania. Łączenie scalające jawnie nazywa go odpowiednikiem Predykatu rezydualnego, a dopasowanie mieszania nazywa go resztkowym sondą (które można uzyskać od TSA, jeśli dopasujesz hash). Ale w Poszukiwaniu nazywają to Predicate, co sprawia, że ​​wydaje się mniej zły niż jest.

Rob Farley
źródło
3

Funkcja GetItemToProcessIndex nie jest w pełni widoczna, ponieważ włączona jest klauzula where ItemState + LastAccessTime + CreationTime. Indeksowane kolumny i klauzula where nie pasują idealnie.

Jeśli utworzysz indeks obejmujący ItemState + LastAccessTime + CreationTime, dla każdego dopasowania uzyskanego z GetItemToProcessIndex otrzymasz również wartość swojego klucza podstawowego (ItemId). Musi tylko upewnić się, że druga data jest zgodna.

To wszystko, czego potrzebujesz, aby przejść do lokalizacji wiersza na jej stronie i zaktualizować ją.

Przy obecnym indeksie może pomóc serwerowi znaleźć wiersze o żądanym elemencie ItemState, ale nadal musi odczytać wszystkie z indeksu, aby znaleźć prawidłowe dopasowania w LastAccessTime + CreationTime. W zależności od predykatów daty i wielkości pasującego zestawu oraz tego, co należy wykluczyć, może to spowodować znacznie więcej IO niż idealnie obejmujący indeks tylko w 3 kolumnach, które szukałyby elementu ItemState i drugiej kolumny (pierwsza data indeksowania) . Druga data zindeksowana może być jednak uwzględniona. Dodatkowe kolumny nie powinny być indeksowane między tymi 3, chociaż może być w porządku jako 4. kolumna (patrz odpowiedź Roba na temat dodatkowych kolumn).

Julien Vavasseur
źródło