Nechápejte mě špatně; Miluji filtrované indexy. Vytvářejí příležitosti pro mnohem efektivnější využití I/O a konečně nám umožňují implementovat správná jedinečná omezení vyhovující ANSI (kde je povoleno více než jedno NULL). K dokonalosti však mají daleko. Chtěl jsem poukázat na několik oblastí, kde by bylo možné filtrované indexy vylepšit a učinit je mnohem užitečnějšími a praktičtějšími pro velkou část zátěže.
Nejprve dobrá zpráva
Filtrované indexy mohou velmi rychle pracovat s dříve drahými dotazy, a to s využitím menšího prostoru (a tedy sníženého I/O, i když jsou skenovány).
Rychlý příklad pomocí Sales.SalesOrderDetailEnlarged (postaveno pomocí tohoto skriptu Jonathanem Kehayiasem (@SQLPoolBoy)). Tato tabulka má 4,8MM řádky s 587 MB dat a 363 MB indexů. Existuje pouze jeden sloupec s možnou hodnotou Null, CarrierTrackingNumber , tak si s tím pohrajeme. Tabulka má v současnosti přibližně polovinu těchto hodnot (2,4MM) jako NULL. Snížím to na přibližně 240 kB, abych simuloval scénář, kdy je pro index skutečně vhodné malé procento řádků v tabulce, abych co nejlépe zvýraznil výhody filtrovaného indexu. Následující dotaz má vliv na 2,17 milionu řádků, takže pro CarrierTrackingNumber zůstane 241 507 řádků s hodnotou NULL :
UPDATE Sales.SalesOrderDetailEnlarged
SET CarrierTrackingNumber = 'x'
WHERE CarrierTrackingNumber IS NULL
AND SalesOrderID % 10 <> 3; Nyní řekněme, že existuje obchodní požadavek, kdy chceme neustále kontrolovat objednávky, které mají produkty, kterým ještě nebylo přiděleno sledovací číslo (předpokládejme objednávky, které jsou rozděleny a odesílány samostatně). V aktuální tabulce bychom spustili tyto dotazy (a přidal jsem příkazy DBCC, abychom v každém případě zajistili chladnou mezipaměť):
DBCC DROPCLEANBUFFERS; DBCC FREEPROCCACHE; SELECT COUNT(*) FROM Sales.SalesOrderDetailEnlarged WHERE CarrierTrackingNumber IS NULL; SELECT ProductID, SalesOrderID FROM Sales.SalesOrderDetailEnlarged WHERE CarrierTrackingNumber IS NULL;
Které vyžadují skenování seskupených indexů a poskytují následující metriky běhu (jak jsou zachyceny pomocí SQL Sentry Plan Explorer):

Za "starých" časů (to znamená od SQL Server 2005) bychom vytvořili tento index (a ve skutečnosti i v SQL Server 2012 je to index, který SQL Server doporučuje):
CREATE INDEX IX_NotVeryHelpful ON [Sales].[SalesOrderDetailEnlarged] ([CarrierTrackingNumber]) INCLUDE ([SalesOrderID],[ProductID]);
S tímto indexem a opětovným spuštěním výše uvedených dotazů jsou zde uvedeny metriky, přičemž oba dotazy používají hledání indexu, jak byste mohli očekávat:

A pak tento index vypustit a vytvořit trochu jiný, jednoduše přidat WHERE klauzule:
CREATE INDEX IX_Filtered_CTNisNULL ON [Sales].[SalesOrderDetailEnlarged] ([CarrierTrackingNumber]) INCLUDE ([SalesOrderID],[ProductID]) WHERE CarrierTrackingNumber IS NULL;
Dostáváme tyto výsledky a oba dotazy používají pro svá hledání filtrovaný index:

Zde je další prostor požadovaný každým indexem ve srovnání se snížením doby běhu a I/O výše uvedených dotazů:
| Index | Indexový prostor | Přidán prostor | Trvání | Čte |
|---|---|---|---|---|
| Žádný vyhrazený index | 363 MB | 15 700 ms | ~164 000 | |
| Nefiltrovaný index | 530 MB | 167 MB (+46 %) | 169 ms | 1 084 |
| Filtrovaný index | 367 MB | 4 MB (+1 %) | 170 ms | 1 084 |
Jak tedy můžete vidět, filtrovaný index poskytuje zlepšení výkonu, která jsou téměř totožná s nefiltrovaným indexem (protože oba jsou schopni získat svá data pomocí stejného počtu čtení), ale s mnohem menším úložištěm náklady, protože filtrovaný index musí ukládat a udržovat pouze řádky, které odpovídají predikátu filtru.
Nyní vraťme tabulku do původního stavu:
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) a Michelle Ufford (@sqlfool) odvedli fantastickou práci, když svým vlastním způsobem nastínili výkonnostní výhody filtrovaných indexů a měli byste se také podívat na jejich příspěvky:
- Michelle Ufford:Filtrované indexy:Co potřebujete vědět
- Tim Chapman:Radosti z filtrovaných indexů
Také jedinečná omezení v souladu s ANSI (druh)
Myslel jsem, že bych se také krátce zmínil o jedinečných omezeních vyhovujících ANSI. V SQL Server 2005 bychom vytvořili jedinečné omezení takto:
CREATE TABLE dbo.Personnel ( EmployeeID INT PRIMARY KEY, SSN CHAR(9) NULL, -- ... other columns ... CONSTRAINT UQ_SSN UNIQUE(SSN) );
(Můžeme také vytvořit jedinečný index bez klastrů namísto omezení; základní implementace je v podstatě stejná.)
Nyní to není žádný problém, pokud jsou SSN známa v době vstupu:
INSERT dbo.Personnel(EmployeeID, SSN) VALUES(1,'111111111'),(2,'111111112');
Je také v pořádku, pokud máme příležitostné SSN, které není známo v době vstupu (předpokládejme žadatele o vízum nebo možná dokonce zahraničního pracovníka, který nemá SSN a nikdy mít nebude):
INSERT dbo.Personnel(EmployeeID, SSN) VALUES(3,NULL);
Zatím je vše dobré. Ale co se stane, když máme sekundu zaměstnanec s neznámým SSN?
INSERT dbo.Personnel(EmployeeID, SSN) VALUES(4,NULL);
Výsledek:
Msg 2627, Level 14, State 1, Line 1Porušení omezení UNIQUE KEY 'UQ_SSN'. Nelze vložit duplicitní klíč do objektu 'dbo.Personnel'. Hodnota duplicitního klíče je (
Příkaz byl ukončen.
V každém okamžiku tedy může v tomto sloupci existovat pouze jedna hodnota NULL. Na rozdíl od většiny scénářů se jedná o jeden případ, kdy SQL Server považuje dvě hodnoty NULL za stejné (spíše než určení, že rovnost je jednoduše neznámá a naopak nepravdivá). Lidé si na tuto nekonzistenci stěžují již léta.
Pokud je to požadavek, můžeme to nyní obejít pomocí filtrovaných indexů:
ALTER TABLE dbo.Personnel DROP CONSTRAINT UQ_SSN; GO CREATE UNIQUE INDEX UQ_SSN ON dbo.Personnel(SSN) WHERE SSN IS NOT NULL;
Nyní naše 4. vložka funguje dobře, protože jedinečnost je vynucena pouze u hodnot, které nejsou NULL. Toto je druh podvádění, ale splňuje základní požadavky, které zamýšlel standard ANSI (i když nám SQL Server neumožňuje použít ALTER TABLE ... ADD CONSTRAINT syntaxe k vytvoření filtrovaného jedinečného omezení).
Ale podržte telefon
Toto jsou skvělé příklady toho, co můžeme dělat s filtrovanými indexy, ale existuje mnoho věcí, které stále neumíme, a několik omezení a problémů, které z toho vyplývají.
Aktualizace statistik
Toto je IMHO jedno z nejdůležitějších omezení. Filtrované indexy nemají prospěch z automatické aktualizace statistik na základě procentuální změny podmnožiny tabulky, která je identifikována predikátem filtru; je založen (jako všechny nefiltrované indexy) na churn proti celé tabulce. To znamená, že v závislosti na tom, jaké procento tabulky je ve filtrovaném indexu, se počet řádků v indexu může zčtyřnásobit nebo snížit na polovinu a statistiky se neaktualizují, pokud to neuděláte ručně. Kimberly Tripp o tom poskytla několik skvělých informací (a Gail Shaw uvádí příklad, kdy trvalo 257 000 aktualizací, než byly aktualizovány statistiky pro filtrovaný index, který obsahoval pouze 10 000 řádků):
https://www.sqlskills.com/blogs/kimberly/filtered-indexes-and-filtered-stats-might-become-seriously-out-of-date/
https://www.sqlskills.com/ blogs/kimberly/category/filtered-indexes/
Také Kimberlyin kolega, Joe Sack (@JosephSack), zadal položku Connect, která navrhuje opravit toto chování pro filtrované indexy i filtrované statistiky.
Omezení výrazu filtru
Existuje několik konstrukcí, které nemůžete použít v predikátu filtru, například NOT IN , OR a dynamické / nedeterministické predikáty jako WHERE col >= DATEADD(DAY, -1, GETDATE()) . Optimalizátor také nemusí rozpoznat filtrovaný index, pokud predikát přesně neodpovídá WHERE klauzule v definici indexu. Zde je několik položek Connect, které se zde snaží získat podporu pro lepší pokrytí:
| Filtrovaný index neumožňuje filtry na disjunkcích | (uzavřeno:podle návrhu) |
| Vytvoření filtrovaného indexu se nezdařilo s klauzulí NOT IN | (uzavřeno:podle návrhu) |
| Podpora pro složitější klauzuli WHERE ve filtrovaných indexech | (aktivní) |
Další potenciální využití v současné době není možné
V současné době nemůžeme vytvořit filtrovaný index na trvalém vypočítaném sloupci, i když je deterministický. Nemůžeme nasměrovat cizí klíč na jedinečný filtrovaný index; pokud chceme, aby index podporoval cizí klíč navíc k dotazům podporovaným filtrovaným indexem, musíme vytvořit druhý, redundantní, nefiltrovaný index. A zde je několik dalších podobných omezení, která byla buď přehlížena, nebo dosud nebyla brána v úvahu:
| Mělo by být možné vytvořit filtrovaný index na deterministickém trvalém vypočítaném sloupci | (aktivní) |
| Povolit filtrovaný jedinečný index jako kandidátský klíč pro cizí klíč | (aktivní) |
| možnost vytvářet indexy filtrů pro indexovaná zobrazení | (zavřeno:neopraví se) |
| Chyba dělení 1908 – Vylepšení dělení | (zavřeno:neopraví se) |
| VYTVOŘIT "FILTROVANÝ" REJSTŘÍK SLOUPCE | (aktivní) |
Problémy s MERGE
A MERGE se znovu objeví na mém seznamu „pozor na to:
| MERGE vyhodnocuje filtrovaný index na řádek, nikoli po operaci, což způsobuje porušení filtrovaného indexu | (zavřeno:neopraví se) |
| Aktualizace MERGE se nezdaří s filtrovaným indexem | (uzavřeno:opraveno) |
| Chyba příkazu MERGE při použití a filtrování indexu INSERT/DELETE | (aktivní) |
| MERGE nesprávně hlásí porušení jedinečných klíčů | (aktivní) |
I když jedna z těchto (zdánlivě úzce souvisejících) chyb říká, že je opravena v SQL Server 2012, možná budete muset kontaktovat PSS, pokud narazíte na jakoukoli variantu tohoto problému, zejména v dřívějších verzích (nebo přestaňte používat MERGE , jak jsem již naznačil).
Omezení nástrojů / DMV / vestavěných
Existuje mnoho DMV, DBCC příkazů, systémových procedur a klientských nástrojů, na které se postupem času začínáme spoléhat. Ne všechny tyto věci jsou však aktualizovány, aby mohly využívat nové funkce; filtrované indexy nejsou výjimkou. Následující položky Connectu upozorňují na některé problémy, které vás mohou podrazit, pokud očekáváte, že budou pracovat s filtrovanými indexy:
| Neexistuje způsob, jak vytvořit filtrovaný index z SSMS při navrhování nové tabulky | (zavřeno:neopraví se) |
| Výraz filtru filtrovaného indexu je ztracen, když je tabulka upravena Návrhářem tabulek | (zavřeno:neopraví se) |
| Návrhář tabulek neskriptuje klauzuli WHERE ve filtrovaných indexech | (aktivní) |
| Návrhář tabulky SSMS nezachovává výraz indexového filtru při přestavbě tabulky | (zavřeno:neopraví se) |
| DBCC PAGE nesprávný výstup s filtrovanými indexy | (aktivní) |
| Návrhy filtrovaného indexu SQL 2008 ze zobrazení DM a DTA | (zavřeno:neopraví se) |
| Vylepšení chybějících indexů DMV pro filtrované indexy | (zavřeno:neopraví se) |
| Chyba syntaxe při replikaci komprimovaných filtrovaných indexů | (zavřeno:neopraví se) |
| Agent:Úlohy používají při spouštění skriptu T-SQL jiné než výchozí možnosti | (zavřeno:neopraví se) |
| Zobrazení závislostí se nezdaří s chybou Transact-SQL 515 | (aktivní) |
| U určitých objektů selže zobrazení závislostí | (zavřeno:neopraví se) |
| V porovnání schématu pro dvě databáze nebyly zjištěny rozdíly v možnostech indexu | (zavřeno:externí) |
| Navrhnout vystavení podmínky filtru indexu ve všech zobrazeních informací o indexu | (zavřeno:neopraví se) |
| Výsledky sp_helpIndex by měly obsahovat výraz filtru indexů filtru | (aktivní) |
| Přetížení sp_help, sp_columns, sp_helpindex pro funkce roku 2008 | (zavřeno:neopraví se) |
U posledních tří nezadržujte dech – je nepravděpodobné, že by Microsoft investoval čas do procedur sp_, DMV, zobrazení INFORMATION_SCHEMA atd. Místo toho se podívejte na přepisy sp_helpindex od Kimberly Tripp, které obsahují informace o filtrovaných indexech s dalšími novými funkcemi, které Microsoft zanechal.
Omezení Optimalizátoru
Existuje několik položek Connect, které popisují případy, kdy filtrované indexy *mohl* použít optimalizátor, ale místo toho jsou ignorovány. V některých případech nejsou považovány za „chyby“, ale spíše za „mezery ve funkčnosti“…
| SQL nepoužívá filtrovaný index na jednoduchý dotaz | (uzavřeno:podle návrhu) |
| Plán provádění filtrovaného indexu není optimalizován | (zavřeno:neopraví se) |
| Nepoužit filtrovaný index a vyhledávání klíčů bez výstupu | (zavřeno:neopraví se) |
| Použití filtrovaného indexu ve sloupci BIT závisí na přesném výrazu SQL použitém v klauzuli WHERE | (aktivní) |
| Dotaz propojeného serveru se neoptimalizuje správně, pokud existuje filtrovaný jedinečný index | (zavřeno:neopraví se) |
| Row_Number() poskytuje nepředvídatelné výsledky na propojených serverech, kde se používají filtrované indexy | (uzavřeno:žádné opakování) |
| Zjevně filtrovaný index nepoužívá QP | (uzavřeno:podle návrhu) |
| Rozpoznat jedinečné filtrované indexy jako jedinečné | (aktivní) |
Paul White (@SQL_Kiwi) nedávno zveřejnil zde na SQLPerformance.com příspěvek, který velmi podrobně popisuje několik výše uvedených omezení optimalizátoru.
A Tim Chapman napsal skvělý příspěvek, který nastínil některá další omezení filtrovaných indexů – například nemožnost spárovat predikát s lokální proměnnou (opraveno v 2008 R2 SP1) a nemožnost specifikovat filtrovaný index v nápovědě k indexu.
Závěr
Filtrované indexy mají velký potenciál a vkládal jsem do nich extrémně velké naděje, když byly poprvé představeny v SQL Server 2008. Většina omezení dodávaných s jejich první verzí však stále existuje dodnes, jeden a půl (nebo dvě, v závislosti na vaší perspektiva) hlavní verze později. Výše uvedené vypadá jako docela obsáhlý seznam položek, které je třeba řešit, ale neměl jsem v úmyslu, aby to tak bylo. Chci jen, aby si lidé byli vědomi obrovského množství potenciálních problémů, které mohou potřebovat zvážit, když využívají filtrované indexy.