Nie zrozum mnie źle; Uwielbiam filtrowane indeksy. Stwarzają możliwości znacznie wydajniejszego wykorzystania I/O i wreszcie pozwalają nam zaimplementować odpowiednie, unikalne ograniczenia zgodne z ANSI (gdzie dozwolone jest więcej niż jedno NULL). Jednak daleko im do ideału. Chciałem wskazać kilka obszarów, w których filtrowane indeksy można ulepszyć i uczynić je znacznie bardziej użytecznymi i praktycznymi dla dużej części obciążeń.
Po pierwsze, dobra wiadomość
Filtrowane indeksy mogą wykonać bardzo szybką pracę wcześniej kosztownych zapytań i robią to z wykorzystaniem mniejszej ilości miejsca (a co za tym idzie, zredukowane I/O, nawet podczas skanowania).
Szybki przykład z użyciem Sales.SalesOrderDetailEnlarged
(zbudowany przy użyciu tego skryptu przez Jonathana Kehayiasa (@SQLPoolBoy)). Ta tabela ma 4,8 mm wierszy, 587 MB danych i 363 MB indeksów. Istnieje tylko jedna kolumna dopuszczająca wartość null, CarrierTrackingNumber
, więc pobawmy się tym. Tak jak jest, tabela ma obecnie około połowę tych wartości (2,4 mm) jako NULL. Zamierzam zmniejszyć to do około 240K, aby zasymulować scenariusz, w którym niewielki procent wierszy w tabeli faktycznie kwalifikuje się do indeksu, aby jak najlepiej podkreślić zalety filtrowanego indeksu. Poniższe zapytanie dotyczy 2,17 mln wierszy, pozostawiając 241,507 wierszy z wartością NULL dla CarrierTrackingNumber
:
UPDATE Sales.SalesOrderDetailEnlarged SET CarrierTrackingNumber = 'x' WHERE CarrierTrackingNumber IS NULL AND SalesOrderID % 10 <> 3;
Załóżmy teraz, że istnieje wymóg biznesowy polegający na ciągłym przeglądaniu zamówień zawierających produkty, którym jeszcze nie przypisano numeru śledzenia (pomyśl o zamówieniach, które są podzielone i wysyłane osobno). W bieżącej tabeli uruchomilibyśmy te zapytania (i dodałem polecenia DBCC, aby zapewnić zimną pamięć podręczną w każdym przypadku):
DBCC DROPCLEANBUFFERS; DBCC FREEPROCCACHE; SELECT COUNT(*) FROM Sales.SalesOrderDetailEnlarged WHERE CarrierTrackingNumber IS NULL; SELECT ProductID, SalesOrderID FROM Sales.SalesOrderDetailEnlarged WHERE CarrierTrackingNumber IS NULL;
Które wymagają skanowania indeksów klastrowych i dają następujące metryki środowiska wykonawczego (jak przechwycone za pomocą programu SQL Sentry Plan Explorer):
W „starych” czasach (czyli od SQL Server 2005) stworzylibyśmy ten indeks (a w rzeczywistości nawet w SQL Server 2012 jest to indeks zalecany przez SQL Server):
CREATE INDEX IX_NotVeryHelpful ON [Sales].[SalesOrderDetailEnlarged] ([CarrierTrackingNumber]) INCLUDE ([SalesOrderID],[ProductID]);
Po umieszczeniu tego indeksu i ponownym uruchomieniu powyższych zapytań, oto metryki, przy czym oba zapytania używają wyszukiwania indeksu, jak można się spodziewać:
A następnie upuszczając ten indeks i tworząc nieco inny, po prostu dodając WHERE
klauzula:
CREATE INDEX IX_Filtered_CTNisNULL ON [Sales].[SalesOrderDetailEnlarged] ([CarrierTrackingNumber]) INCLUDE ([SalesOrderID],[ProductID]) WHERE CarrierTrackingNumber IS NULL;
Otrzymujemy te wyniki, a oba zapytania używają filtrowanego indeksu do swoich wyszukiwań:
Oto dodatkowa przestrzeń wymagana przez każdy indeks w porównaniu ze skróceniem czasu wykonywania i we/wy powyższych zapytań:
Indeks | Przestrzeń indeksu | Dodano miejsce | Czas trwania | Czyta |
---|---|---|---|---|
Brak dedykowanego indeksu | 363 MB | 15 700 ms | ~164 000 | |
Indeks niefiltrowany | 530 MB | 167 MB (+46%) | 169 ms | 1084 |
Indeks filtrowany | 367 MB | 4 MB (+1%) | 170 ms | 1084 |
Więc, jak widać, filtrowany indeks zapewnia poprawę wydajności, która jest prawie identyczna jak indeks niefiltrowany (ponieważ oba są w stanie uzyskać swoje dane przy użyciu tej samej liczby odczytów), ale przy znacznie mniejszej pamięci koszt, ponieważ filtrowany indeks musi przechowywać i utrzymywać tylko wiersze, które pasują do predykatu filtra.
Teraz przywróćmy stół do pierwotnego stanu:
UPDATE Sales.SalesOrderDetailEnlarged SET CarrierTrackingNumber = NULL WHERE CarrierTrackingNumber = 'x'; DROP INDEX IX_NotVeryHelpful ON Sales.SalesOrderDetailEnlarged; DROP INDEX IX_Filtered_CTNisNULL ON Sales.SalesOrderDetailEnlarged;
Tim Chapman (@chapmandew) i Michelle Ufford (@sqlfool) wykonali fantastyczną robotę, opisując na swój sposób zalety wydajności filtrowanych indeksów. Powinieneś również sprawdzić ich posty:
- Michelle Ufford:Filtrowane indeksy:co musisz wiedzieć
- Tim Chapman:Radości z filtrowanych indeksów
Ponadto unikatowe ograniczenia zgodne z ANSI (tak jakby)
Pomyślałem, że wspomnę też krótko o unikalnych ograniczeniach zgodnych z ANSI. W SQL Server 2005 utworzylibyśmy takie ograniczenie unikatowe:
CREATE TABLE dbo.Personnel ( EmployeeID INT PRIMARY KEY, SSN CHAR(9) NULL, -- ... other columns ... CONSTRAINT UQ_SSN UNIQUE(SSN) );
(Możemy również utworzyć unikalny indeks nieklastrowy zamiast ograniczenia; podstawowa implementacja jest zasadniczo taka sama.)
Nie stanowi to problemu, jeśli numery SSN są znane w momencie wpisywania:
INSERT dbo.Personnel(EmployeeID, SSN) VALUES(1,'111111111'),(2,'111111112');
Jest również w porządku, jeśli mamy okazjonalny numer SSN, który nie jest znany w momencie wjazdu (pomyśl o wizowym lub nawet pracowniku zagranicznym, który nie ma numeru SSN i nigdy go nie będzie):
INSERT dbo.Personnel(EmployeeID, SSN) VALUES(3,NULL);
Jak na razie dobrze. Ale co się dzieje, gdy mamy sekundę pracownik z nieznanym numerem SSN?
INSERT dbo.Personnel(EmployeeID, SSN) VALUES(4,NULL);
Wynik:
Msg 2627, poziom 14, stan 1, wiersz 1Naruszenie ograniczenia UQ_SSN 'UQ_SSN'. Nie można wstawić zduplikowanego klucza w obiekcie „dbo.Personnel”. Zduplikowana wartość klucza to (
Instrukcja została zakończona.
Tak więc w każdej chwili w tej kolumnie może istnieć tylko jedna wartość NULL. W przeciwieństwie do większości scenariuszy, jest to jeden przypadek, w którym SQL Server traktuje dwie wartości NULL jako równe (zamiast określać, że równość jest po prostu nieznana i z kolei fałszywa). Ludzie od lat narzekają na tę niespójność.
Jeśli jest to wymagane, możemy teraz obejść ten problem za pomocą filtrowanych indeksów:
ALTER TABLE dbo.Personnel DROP CONSTRAINT UQ_SSN; GO CREATE UNIQUE INDEX UQ_SSN ON dbo.Personnel(SSN) WHERE SSN IS NOT NULL;
Teraz nasza czwarta wstawka działa dobrze, ponieważ unikalność jest wymuszana tylko na wartościach innych niż NULL. Jest to rodzaj oszustwa, ale spełnia podstawowe wymagania przewidziane przez standard ANSI (mimo że SQL Server nie pozwala nam na użycie ALTER TABLE ... ADD CONSTRAINT
składnia, aby utworzyć filtrowane ograniczenie unikatowe).
Ale trzymaj telefon
To świetne przykłady tego, co możemy zrobić z filtrowanymi indeksami, ale wciąż jest wiele rzeczy, których nie możemy zrobić, a także kilka ograniczeń i problemów, które mogą się pojawić.
Aktualizacje statystyk
Jest to jedno z ważniejszych ograniczeń IMHO. Indeksy filtrowane nie korzystają z automatycznej aktualizacji statystyk na podstawie procentowej zmiany podzbioru tabeli, który jest identyfikowany przez predykat filtru; opiera się (podobnie jak wszystkie niefiltrowane indeksy) na odrzuceniu całej tabeli. Oznacza to, że w zależności od tego, jaki procent tabeli znajduje się w filtrowanym indeksie, liczba wierszy w indeksie może się czterokrotnie lub o połowę, a statystyki nie zostaną zaktualizowane, chyba że zrobisz to ręcznie. Kimberly Tripp podała kilka świetnych informacji na ten temat (a Gail Shaw przytacza przykład, w którym zaktualizowano 257 000 aktualizacji, zanim statystyki zostały zaktualizowane dla przefiltrowanego indeksu, który zawierał tylko 10 000 wierszy):
http://www.sqlskills.com/blogs/kimberly/filtered-indexes-and-filtered-stats-might-become-seriously-out-of-date/
http://www.sqlskills.com/ blogi/kimberly/kategoria/filtrowane-indeksy/
Ponadto kolega Kimberly, Joe Sack (@JosephSack), zgłosił element Connect, który sugeruje poprawienie tego zachowania zarówno dla filtrowanych indeksów, jak i filtrowanych statystyk.
Ograniczenia wyrażeń filtrów
Istnieje kilka konstrukcji, których nie można używać w predykacie filtra, takich jak NOT IN
, OR
oraz predykaty dynamiczne / niedeterministyczne, takie jak WHERE col >= DATEADD(DAY, -1, GETDATE())
. Ponadto optymalizator może nie rozpoznać filtrowanego indeksu, jeśli predykat nie pasuje dokładnie do WHERE
klauzula w definicji indeksu. Oto kilka elementów Connect, które próbują uzyskać wsparcie dla lepszego zasięgu:
Indeks filtrowany nie pozwala na filtry przy rozłączeniach | (zamknięte:zgodnie z projektem) |
Utworzenie indeksu filtrowanego nie powiodło się z klauzulą NOT IN | (zamknięte:zgodnie z projektem) |
Obsługa bardziej złożonej klauzuli WHERE w filtrowanych indeksach | (aktywny) |
Inne potencjalne zastosowania obecnie nie są możliwe
Obecnie nie możemy utworzyć filtrowanego indeksu na utrwalonej kolumnie wyliczanej, nawet jeśli jest deterministyczny. Nie możemy wskazać klucza obcego na unikalny filtrowany indeks; jeśli chcemy, aby indeks obsługiwał klucz obcy oprócz zapytań obsługiwanych przez indeks filtrowany, musimy utworzyć drugi, nadmiarowy, niefiltrowany indeks. A oto kilka innych podobnych ograniczeń, które albo zostały przeoczone, albo nie zostały jeszcze uwzględnione:
Powinno być możliwe utworzenie filtrowanego indeksu na deterministycznej utrwalonej kolumnie obliczeniowej | (aktywny) |
Zezwól filtrowany unikalny indeks na klucz kandydujący dla klucza obcego | (aktywny) |
możliwość tworzenia indeksów filtrów na widokach indeksowanych | (zamknięte:nie naprawi) |
Błąd partycjonowania 1908 – Ulepsz partycjonowanie | (zamknięte:nie naprawi) |
UTWÓRZ „FILTROWANY” INDEKS MAGAZYNU KOLUMN | (aktywny) |
Problemy ze scalaniem
I MERGE
pojawia się jeszcze raz na mojej liście „uważaj”:
MERGE ocenia filtrowany indeks na wiersz, a nie po operacji, co powoduje naruszenie filtrowanego indeksu | (zamknięte:nie naprawi) |
MERGE nie aktualizuje się z filtrowanym indeksem | (zamknięte:naprawione) |
Błąd instrukcji MERGE, gdy użyto INSERT/DELETE i przefiltrowano indeks | (aktywny) |
MERGE nieprawidłowo zgłasza unikalne naruszenia kluczy | (aktywny) |
Chociaż jeden z tych (pozornie blisko powiązanych) błędów mówi, że został naprawiony w SQL Server 2012, może być konieczne skontaktowanie się z PSS, jeśli napotkasz jakąkolwiek odmianę tego problemu, szczególnie we wcześniejszych wersjach (lub przestaniesz używać MERGE , jak sugerowałem wcześniej).
Narzędzia / DMV / ograniczenia wbudowane
Istnieje wiele DMV, poleceń DBCC, procedur systemowych i narzędzi klienckich, na których zaczynamy z czasem polegać. Jednak nie wszystkie te rzeczy są aktualizowane w celu wykorzystania nowych funkcji; indeksy filtrowane nie są wyjątkiem. Poniższe elementy Connect wskazują na pewne problemy, które mogą Cię napotkać, jeśli spodziewasz się, że będą działać z filtrowanymi indeksami:
Nie ma możliwości utworzenia filtrowanego indeksu z SSMS podczas projektowania nowej tabeli | (zamknięte:nie naprawi) |
Wyrażenie filtru filtrowanego indeksu jest tracone, gdy tabela jest modyfikowana przez projektanta tabel | (zamknięte:nie naprawi) |
Projektant tabel nie skryptuje klauzuli WHERE w filtrowanych indeksach | (aktywny) |
Projektant tabel SSMS nie zachowuje wyrażenia filtru indeksu podczas przebudowy tabeli | (zamknięte:nie naprawi) |
Nieprawidłowe wyjście DBCC PAGE z filtrowanymi indeksami | (aktywny) |
Sugestie filtrowanego indeksu SQL 2008 z widoków DM i DTA | (zamknięte:nie naprawi) |
Ulepszenia brakujących indeksów DMV dla filtrowanych indeksów | (zamknięte:nie naprawi) |
Błąd składni podczas replikowania skompresowanych filtrowanych indeksów | (zamknięte:nie naprawi) |
Agent:zadania używają niestandardowych opcji podczas uruchamiania skryptu T-SQL | (zamknięte:nie naprawi) |
Wyświetl zależności kończy się niepowodzeniem z powodu błędu Transact-SQL 515 | (aktywny) |
Wyświetl zależności nie działa na niektórych obiektach | (zamknięte:nie naprawi) |
Różnice opcji indeksu nie są wykrywane w porównaniu schematu dla dwóch baz danych | (zamknięte:zewnętrzne) |
Zaproponuj ujawnienie warunku filtra indeksu we wszystkich widokach informacji o indeksie | (zamknięte:nie naprawi) |
Wyniki sp_helpIndex powinny zawierać wyrażenie Filter indeksów filtra | (aktywny) |
Przeciążenie sp_help, sp_columns, sp_helpindex dla funkcji 2008 | (zamknięte:nie naprawi) |
W przypadku ostatnich trzech nie wstrzymuj oddechu – Microsoft raczej nie zainwestuje czasu w procedury sp_, DMV, widoki INFORMATION_SCHEMA itp. Zamiast tego zobacz przeróbki sp_helpindex Kimberly Tripp, które zawierają informacje o filtrowanych indeksach z innymi nowymi funkcjami pozostawionymi przez Microsoft.
Ograniczenia Optymalizatora
Istnieje kilka elementów Connect, które opisują przypadki, w których filtrowane indeksy *mogłyby* być używane przez optymalizator, ale zamiast tego są ignorowane. W niektórych przypadkach nie są one uważane za „błędy”, ale raczej za „luki w funkcjonalności”…
SQL nie używa filtrowanego indeksu w prostym zapytaniu | (zamknięte:zgodnie z projektem) |
Plan wykonania filtrowanego indeksu nie jest zoptymalizowany | (zamknięte:nie naprawi) |
Indeks filtrowany nie jest używany i wyszukiwanie kluczy bez danych wyjściowych | (zamknięte:nie naprawi) |
Użycie indeksu filtrowanego w kolumnie BIT zależy od dokładnego wyrażenia SQL użytego w klauzuli WHERE | (aktywny) |
Kwerenda serwera połączonego nie optymalizuje się prawidłowo, gdy istnieje filtrowany unikalny indeks | (zamknięte:nie naprawi) |
Row_Number() daje nieprzewidywalne wyniki na połączonych serwerach, w których używane są filtrowane indeksy | (zamknięte:brak reprodukcji) |
Oczywisty filtrowany indeks nie jest używany przez QP | (zamknięte:zgodnie z projektem) |
Rozpoznawaj unikalne filtrowane indeksy jako unikalne | (aktywny) |
Paul White (@SQL_Kiwi) opublikował niedawno na stronie SQLPerformance.com post, który szczegółowo opisuje kilka powyższych ograniczeń optymalizatora.
Tim Chapman napisał świetny post, w którym przedstawił kilka innych ograniczeń filtrowanych indeksów – takich jak niemożność dopasowania predykatu do zmiennej lokalnej (naprawionej w 2008 R2 SP1) oraz niemożność określenia filtrowanego indeksu we wskazówce indeksu.
Wniosek
Filtrowane indeksy mają ogromny potencjał i wiązałem z nimi ogromne nadzieje, kiedy po raz pierwszy zostały wprowadzone w SQL Server 2008. Jednak większość ograniczeń, które były dostarczane z ich pierwszą wersją, nadal istnieje do dziś, półtora (lub dwa, w zależności od perspektywy) główne wersje później. Powyższe wydaje się być dość obszerną listą rzeczy do prania, którymi należy się zająć, ale nie chciałem, aby pojawiło się to w ten sposób. Chcę tylko, aby ludzie byli świadomi ogromnej liczby potencjalnych problemów, które mogą być konieczne do rozważenia podczas korzystania z filtrowanych indeksów.