Filtrowane indeksy są niesamowicie potężne, ale wciąż widzę pewne zamieszanie związane z nimi – szczególnie jeśli chodzi o kolumny używane w filtrach i co się dzieje, gdy chcesz zaostrzyć filtry.
Niedawne pytanie na dba.stackexchange prosiło o pomoc, dlaczego kolumny używane w filtrze filtrowanego indeksu powinny być uwzględnione w kolumnach „included” indeksu. Doskonałe pytanie – poza tym, że czułem, że zaczęło się od złego założenia, ponieważ te kolumny nie muszą być uwzględnione w indeksie . Tak, pomagają, ale nie w sposób, który sugerowało pytanie.
Aby nie patrzeć na samo pytanie, oto krótkie podsumowanie:
Aby spełnić to zapytanie…
SELECT Id, DisplayName FROM Users WHERE Reputation > 400000;
…następujący filtrowany indeks jest całkiem dobry:
CREATE UNIQUE NONCLUSTERED INDEX Users_400k_Club ON dbo.Users ( DisplayName, Id ) INCLUDE ( Reputation ) WHERE Reputation > 400000;
Ale pomimo posiadania tego indeksu, Optymalizator zapytań zaleca następujący indeks, jeśli filtrowana wartość jest zawężona do, powiedzmy, 450000.
CREATE NONCLUSTERED INDEX IndexThatWasMissing ON dbo.Users ( Reputation ) INCLUDE ( DisplayName, Id );
Parafrazuję tu trochę pytanie, które zaczyna się od odniesienia do tej sytuacji, a następnie buduje inny przykład, ale idea jest ta sama. Po prostu nie chciałem komplikować sprawy przez angażowanie osobnej tabeli.
Point is – indeks sugerowany przez QO jest indeksem oryginalnym, ale odwróconym do góry nogami. Oryginalny indeks miał Reputation na liście INCLUDE oraz DisplayName i Id jako kolumny klucza, podczas gdy nowy zalecany indeks jest odwrotny, z Reputation jako kolumną klucza i DisplayName &ID w INCLUDE. Zastanówmy się, dlaczego.
Pytanie odnosi się do posta Erika Darlinga, w którym wyjaśnia, że dostroił powyższe zapytanie „450 000”, umieszczając Reputację w kolumnie INCLUDE. Erik pokazuje, że bez Reputation na liście INCLUDE, zapytanie filtrujące do wyższej wartości Reputation musi wykonać Lookupy (źle!), a może nawet całkowicie zrezygnować z filtrowanego indeksu (potencjalnie nawet gorzej). Doszedł do wniosku, że posiadanie kolumny Reputacja na liście INCLUDE pozwala SQL mieć statystyki, dzięki czemu może dokonywać lepszych wyborów i pokazuje, że z Reputacją w INCLUDE różne zapytania, które filtrują według wyższych wartości Reputacji, skanują jego filtrowany indeks.
W odpowiedzi na pytanie dba.stackexchange Brent Ozar wskazuje, że ulepszenia Erika nie są szczególnie dobre, ponieważ powodują skanowanie. Wrócę do tego, ponieważ sam w sobie jest interesujący i nieco niepoprawny.
Najpierw zastanówmy się trochę ogólnie o indeksach.
Indeks zapewnia uporządkowaną strukturę zbiorowi danych. (Mógłbym być pedantyczny i zwrócić uwagę, że czytanie danych w indeksie od początku do końca może przeskakiwać ze strony na stronę w pozornie przypadkowy sposób, ale nadal, gdy czytasz kolejne strony, podążając za wskazówkami z jednej strony do w następnej możesz mieć pewność, że dane są uporządkowane.W obrębie każdej strony możesz nawet przeskoczyć, aby odczytać dane w kolejności, ale jest tam lista pokazująca, które części (sloty) strony powinny być czytane w jakiej kolejności. moja pedanteria nie ma sensu, z wyjątkiem odpowiadania tym równie pedantycznym, którzy skomentują, jeśli tego nie zrobię.)
A ta kolejność jest zgodna z kluczowymi kolumnami – to łatwy kawałek, który każdy dostaje. Jest to przydatne nie tylko do uniknięcia późniejszej zmiany kolejności danych, ale także do szybkiego zlokalizowania dowolnego wiersza lub zakresu wierszy według tych kolumn.
Poziomy liści indeksu zawierają wartości we wszystkich kolumnach na liście INCLUDE lub, w przypadku indeksu klastrowanego, wartości we wszystkich kolumnach tabeli (z wyjątkiem nieutrwalonych kolumn obliczanych). Pozostałe poziomy w indeksie zawierają tylko kolumny kluczy i (jeśli indeks nie jest unikalny) unikalny adres wiersza – który jest albo kluczami indeksu klastrowego (z unifikatorem wiersza, jeśli indeks klastrowany nie jest unikalny ) lub wartość RowID dla sterty, wystarczająca, aby umożliwić łatwy dostęp do wszystkich innych wartości kolumn dla wiersza. Poziomy liści zawierają również wszystkie informacje „adresowe”.
Ale to nie jest interesujące w tym poście. Ciekawym fragmentem tego postu jest to, co rozumiem przez „do zestawu danych”. Pamiętaj, że powiedziałem „Indeks zapewnia uporządkowaną strukturę zestawowi danych ".
W indeksie klastrowym ten zestaw danych to cała tabela, ale może to być coś innego. Prawdopodobnie już możesz sobie wyobrazić, że większość indeksów nieklastrowych nie obejmuje wszystkich kolumn tabeli. Jest to jedna z rzeczy, które sprawiają, że indeksy nieklastrowe są tak przydatne, ponieważ zazwyczaj są znacznie mniejsze niż tabela bazowa.
W przypadku widoku indeksowanego, naszym zbiorem danych mogą być wyniki całego zapytania, w tym złączeń w wielu tabelach! To na inny post.
Ale w filtrowanym indeksie nie jest to tylko kopia podzbioru kolumn, ale także podzbiór wierszy. Tak więc w tym przykładzie indeks dotyczy tylko użytkowników z reputacją powyżej 400 tys.
CREATE UNIQUE NONCLUSTERED INDEX Users_400k_Club_NoInclude ON dbo.Users ( DisplayName, Id ) WHERE Reputation > 400000;
Ten indeks pobiera użytkowników, którzy mają ponad 400 tys. reputacji, i porządkuje ich według DisplayName i Id. Może być unikalny, ponieważ (przypuszczalnie) kolumna Id jest już unikalna. Jeśli spróbujesz czegoś podobnego na własnym stole, być może będziesz musiał być ostrożny.
Ale w tym momencie indeks nie dba o to, jaka jest Reputacja dla każdego użytkownika – obchodzi tylko, czy Reputacja jest wystarczająco wysoka, aby znaleźć się w indeksie, czy nie. Jeśli reputacja użytkownika zostanie zaktualizowana i przekroczy próg, do indeksu zostanie wstawiona nazwa wyświetlana i identyfikator użytkownika. Jeśli spadnie poniżej, zostanie usunięty z indeksu. To tak, jakby mieć osobny stół dla graczy, którzy stawiają wysokie stawki, z tą różnicą, że wprowadzamy ludzi do tego stołu, zwiększając ich wartość reputacji powyżej progu 400 tys. w tabeli poniżej. Może to zrobić bez konieczności przechowywania samej wartości reputacji.
Więc teraz, jeśli chcemy znaleźć ludzi, których próg przekracza 450 000, w tym indeksie brakuje niektórych informacji.
Oczywiście, możemy śmiało powiedzieć, że każdy, kogo znajdziemy, znajduje się w tym indeksie – ale indeks sam w sobie nie zawiera wystarczającej ilości informacji, aby dalej filtrować reputację. Gdybym ci powiedział, że mam alfabetyczną listę filmów nagrodzonych Oscarem za najlepszy film z lat 90. (American Beauty, Waleczne serce, Tańce z wilkami, Angielski pacjent, Forrest Gump, Lista Schindlera, Zakochany Szekspir, Milczenie owiec, Titanic, Unforgiven) , to mogę was zapewnić, że zwycięzcy za lata 1994-1996 będą częścią tych, ale nie mogę odpowiedzieć na to pytanie bez uprzedniego uzyskania dodatkowych informacji.
Oczywiście mój filtrowany indeks byłby bardziej przydatny, gdybym uwzględnił rok, a potencjalnie nawet bardziej, gdyby rok był kolumną kluczową, ponieważ moje nowe zapytanie chce znaleźć te z lat 1994-1996. Ale prawdopodobnie zaprojektowałem ten indeks wokół zapytania, aby wymienić wszystkie filmy z lat 90. w porządku alfabetycznym. To zapytanie nie dotyczy tego, jaki jest rzeczywisty rok, tylko czy jest to lata 90., czy nie, i nie muszę nawet zwracać roku – tylko tytuł – więc mogę przeskanować mój filtrowany indeks, aby uzyskać wyniki. W przypadku tego zapytania nie muszę nawet zmieniać kolejności wyników ani znajdować punktu wyjścia – mój indeks jest naprawdę doskonały.
Bardziej praktycznym przykładem niedbania o wartość kolumny w filtrze jest status, taki jak:
WHERE IsActive = 1
Często widzę kod, który przenosi dane z jednej tabeli do drugiej, gdy wiersze przestają być „aktywne”. Ludzie nie chcą, aby stare wiersze zaśmiecały ich tabele i zdają sobie sprawę, że ich „gorące” dane to tylko mały podzbiór wszystkich ich danych. Przenoszą więc swoje dane dotyczące chłodzenia do tabeli Archiwum, utrzymując małą tabelę Active.
Filtrowany indeks może to zrobić za Ciebie. Za kulisami. Jak tylko zaktualizujesz wiersz i zmienisz tę kolumnę IsActive na coś innego niż 1. Jeśli zależy Ci tylko na posiadaniu aktywnych danych w większości indeksów, filtrowane indeksy są idealne. Wprowadzi nawet wiersze z powrotem do indeksów, jeśli wartość IsActive zmieni się z powrotem na 1.
Ale nie musisz umieszczać IsActive na liście INCLUDE, aby to osiągnąć. Dlaczego miałbyś chcieć przechowywać wartość – już wiesz, jaka jest wartość – to 1! Chyba że prosisz o zwrot wartości, nie powinieneś jej potrzebować. A dlaczego miałbyś zwracać wartość, skoro już wiesz, że odpowiedź to 1, prawda?! Tyle że frustrujące jest to, że statystyki, do których odnosi się Erik w swoim poście, wykorzystają obecność na liście INCLUDE. Nie potrzebujesz go do zapytania, ale powinieneś go uwzględnić w statystykach.
Zastanówmy się, co musi zrobić Optymalizator zapytań, aby określić przydatność indeksu.
Zanim będzie w stanie wiele zdziałać, musi zastanowić się, czy indeks jest kandydatem. Nie ma sensu używać indeksu, jeśli nie zawiera on wszystkich potrzebnych wierszy – chyba że mamy skuteczny sposób na uzyskanie reszty. Jeśli chcę mieć filmy z lat 1985-1995, to mój indeks filmów z lat 90. nie ma sensu. Ale na lata 1994-1996 może nie jest źle.
W tym momencie, podobnie jak w przypadku każdego rozważania dotyczącego indeksu, muszę zastanowić się, czy pomoże to wystarczająco do znalezienia danych i uporządkowania ich w kolejności, która pomoże wykonać resztę zapytania (być może dla Merge Join, Stream Aggregate, satysfakcjonującego ORDER BY lub z różnych innych powodów). Jeśli mój filtr zapytania dokładnie pasuje do filtru indeksu, nie muszę dalej filtrować – wystarczy użyć indeksu. Brzmi to świetnie, ale jeśli nie pasuje dokładnie, jeśli mój filtr zapytania jest ściślejszy niż filtr indeksu (jak mój przykład z lat 1994-1996 lub 450 000 Erika), będę potrzebował tych wartości roku lub wartości reputacji do sprawdzenia – miejmy nadzieję, że otrzymam je albo z INCLUDE na poziomie liścia, albo gdzieś w moich kluczowych kolumnach. Jeśli nie ma ich w indeksie, będę musiał wykonać wyszukiwanie dla każdego wiersza w moim przefiltrowanym indeksie (i najlepiej mieć pojęcie o tym, ile razy zostanie wywołany mój wyszukiwanie, które są statystykami, których chce Erik kolumna dołączona do).
W idealnym przypadku każdy indeks, którego planuję użyć, jest uporządkowany poprawnie (za pomocą kluczy), ZAWIERA wszystkie kolumny, które muszę zwrócić, i jest wstępnie filtrowany tylko do potrzebnych wierszy. To byłby idealny indeks, a moim planem wykonania będzie skanowanie.
Zgadza się, SKAN. Nie poszukiwanie, ale skanowanie. Rozpocznie się na pierwszej stronie mojego indeksu i będzie dawać mi wiersze, aż będę miał tyle, ile potrzebuję, lub do momentu, gdy nie będzie więcej wierszy do zwrócenia. Nie pomijam żadnego, nie sortuję – po prostu podajesz mi wiersze w kolejności.
Seek sugerowałby, że nie potrzebuję całego indeksu, co oznacza, że marnuję zasoby na utrzymanie tej części indeksu, a aby wykonać zapytanie, muszę znaleźć punkt początkowy i ciągle sprawdzać wiersze, aby zobaczyć, czy mam trafić do końca, czy nie. Jeśli moje skanowanie ma predykat, to z pewnością muszę przejrzeć (i przetestować) więcej danych niż potrzebuję, ale jeśli moje filtry indeksu są doskonałe, Optymalizator zapytań powinien to rozpoznać i nie musi wykonywać tych sprawdzeń .
Ostateczne myśli
INCLUDE nie są krytyczne dla filtrowanych indeksów. Są one przydatne, aby zapewnić łatwy dostęp do kolumn, które mogą być przydatne w zapytaniu, a jeśli zdarzy ci się zawęzić zawartość filtrowanego indeksu o dowolną kolumnę, niezależnie od tego, czy jest ona wymieniona w filtrze, czy nie, powinieneś rozważyć umieszczenie tej kolumny w mieszanka. Ale w tym momencie powinieneś zapytać, czy filtr twojego indeksu jest właściwy, co jeszcze powinieneś mieć na liście INCLUDE, a nawet jakie kolumny kluczowe powinny być. Zapytania Erika nie działały dobrze, ponieważ potrzebował informacji, których nie było w indeksie, mimo że wspomniał o kolumnie w filtrze. Znalazł również dobre zastosowanie dla statystyk i nadal zachęcam do uwzględnienia kolumn filtrujących z tego powodu. Ale umieszczenie ich w INCLUDE nie pozwala im nagle zacząć wykonywać Seek, ponieważ nie tak działa każdy indeks, niezależnie od tego, czy jest filtrowany, czy nie.
Czytelniku, chcę, abyś naprawdę dobrze zrozumiał filtrowane indeksy. Są niezwykle przydatne, a kiedy zaczniesz je przedstawiać jako własne tabele, mogą stać się częścią ogólnego projektu bazy danych. Są również powodem, dla którego zawsze używasz ustawień ANSI_NULL i QUOTED_IDENTIFIER, ponieważ będziesz otrzymywać błędy z filtrowanego indeksu, chyba że te ustawienia są WŁĄCZONE, ale miejmy nadzieję, że już upewniłeś się, że i tak są zawsze włączone.
Aha, a te filmy to Forrest Gump, Waleczne serce i Angielski pacjent.
@rob_farley