sql >> Databáze >  >> RDS >> Database

Jak by filtrované indexy mohly být výkonnější funkcí

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 1
Poruš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.


  1. Datové typy MySQL:Poznejte, které a jak používat

  2. Chyby ve vývoji na Heroku

  3. Poskytne UUID jako primární klíč v PostgreSQL špatný výkon indexu?

  4. Vytvořte uživatele se všemi oprávněními v Oracle