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ů):
http://www.sqlskills.com/blogs/kimberly/filtered-indexes-and-filtered-stats-might-become-seriously-out-of-date/
http://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.