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

Filtrované indexy a zahrnuté sloupce

Filtrované indexy jsou úžasně výkonné, ale stále v nich vidím určitý zmatek – zejména pokud jde o sloupce, které se používají ve filtrech, a co se stane, když chcete filtry zpřísnit.

Nedávný dotaz na dba.stackexchange požádal o pomoc ohledně toho, proč by sloupce použité ve filtru filtrovaného indexu měly být zahrnuty do „zahrnutých“ sloupců indexu. Výborná otázka – až na to, že jsem měl pocit, že to začalo na špatném předpokladu, protože tyto sloupce nemusí být zahrnuty do indexu . Ano, pomáhají, ale ne tak, jak se zdálo naznačovat otázku.

Abyste se nemuseli dívat na samotnou otázku, zde je rychlé shrnutí:

K uspokojení tohoto dotazu…

SELECT Id, DisplayName 
FROM Users 
WHERE Reputation > 400000;

…následující filtrovaný index je docela dobrý:

CREATE UNIQUE NONCLUSTERED INDEX Users_400k_Club
ON dbo.Users ( DisplayName, Id )
INCLUDE ( Reputation )
WHERE Reputation > 400000;

Ale přestože má tento index zaveden, doporučuje Optimalizátor dotazů následující index, pokud je filtrovaná hodnota zpřísněna například na 450 000.

CREATE NONCLUSTERED INDEX IndexThatWasMissing
ON dbo.Users ( Reputation )
INCLUDE ( DisplayName, Id );

Trochu zde parafrázuji otázku, která začíná odkazem na tuto situaci a pak vytváří jiný příklad, ale myšlenka je stejná. Jen jsem nechtěl věci komplikovat zapojením samostatné tabulky.

Pointa je – index navržený QO je původní index, ale postavený na hlavu. Původní index měl Reputation v seznamu INCLUDE a DisplayName a Id jako klíčové sloupce, zatímco nový doporučený index je opačný s Reputation jako klíčovým sloupcem a DisplayName &ID v INCLUDE. Pojďme se podívat na proč.

Otázka odkazuje na příspěvek Erika Darlinga, kde vysvětluje, že vyladil dotaz „450 000“ výše tím, že do sloupce INCLUDE vložil Reputaci. Erik ukazuje, že bez Reputace v seznamu INCLUDE musí dotaz, který filtruje na vyšší hodnotu Reputace, provádět vyhledávání (špatné!), nebo se možná dokonce úplně vzdát filtrovaného indexu (potenciálně ještě horší). Došel k závěru, že sloupec Reputace v seznamu INCLUDE umožňuje SQL mít statistiky, takže může dělat lepší rozhodnutí, a ukazuje, že s Reputation v seznamu INCLUDE různé dotazy, které všechny filtrují na vyšších hodnotách Reputace, skenují jeho filtrovaný index.

V odpovědi na otázku dba.stackexchange Brent Ozar poukazuje na to, že Erikova vylepšení nejsou nijak zvlášť velká, protože způsobují Scany. Vrátím se k tomu, protože je to zajímavý bod sám o sobě a poněkud nesprávný.

Nejprve se trochu zamysleme nad indexy obecně.

Index poskytuje množině dat uspořádanou strukturu. (Mohl bych být pedantský a podotknout, že čtení dat v indexu od začátku do konce vás může přeskakovat ze stránky na stránku zdánlivě nahodilým způsobem, ale přesto, když čtete stránky, sledujete ukazatele z jedné stránky na další si můžete být jisti, že jsou data seřazena. Na každé stránce můžete dokonce přeskakovat a přečíst si data v pořadí, ale existuje seznam, který vám ukazuje, které části (sloty) stránky by se měly číst v jakém pořadí. nemá smysl v mé pedantnosti, než odpovídat těm stejně pedantským, kteří budou komentovat, když to neudělám.)

A toto pořadí je podle klíčových sloupců – to je snadný kousek, který dostane každý. Je to užitečné nejen proto, že se můžete vyhnout pozdějšímu přeuspořádání dat, ale také proto, že můžete rychle najít jakýkoli konkrétní řádek nebo rozsah řádků podle těchto sloupců.

Úrovně listů indexu obsahují hodnoty ve všech sloupcích v seznamu INCLUDE nebo v případě seskupeného indexu hodnoty ve všech sloupcích v tabulce (kromě nepřetržitých počítaných sloupců). Ostatní úrovně v indexu obsahují pouze klíčové sloupce a (pokud index není jedinečný) jedinečnou adresu řádku – což jsou buď klíče seskupeného indexu (s uniquifikátorem řádku, pokud není seskupený index jedinečný). ) nebo hodnota RowID pro hromadu, dostačující na to, aby umožnila snadný přístup ke všem ostatním hodnotám sloupců pro daný řádek. Úrovně listů také zahrnují všechny informace o „adrese“.

Ale to není pro tento příspěvek to zajímavé. Zajímavé pro tento příspěvek je to, co myslím „k souboru dat“. Pamatujte, že jsem řekl:„Index poskytuje uspořádanou strukturu množině dat ".

."

V klastrovaném indexu je touto sadou dat celá tabulka, ale může to být něco jiného. Pravděpodobně si již dokážete představit, že většina indexů bez klastrů nezahrnuje všechny sloupce tabulky. To je jedna z věcí, díky kterým jsou neklastrované indexy tak užitečné, protože jsou obvykle mnohem menší než podkladová tabulka.

V případě indexovaného zobrazení to může být naše sada dat výsledky celého dotazu, včetně spojení napříč mnoha tabulkami! To je na jiný příspěvek.

Ale ve filtrovaném indexu to není jen kopie podmnožiny sloupců, ale také podmnožiny řádků. Takže v tomto příkladu je index pouze mezi uživateli s více než 400 000 reputací.

CREATE UNIQUE NONCLUSTERED INDEX Users_400k_Club_NoInclude
ON dbo.Users ( DisplayName, Id )
WHERE Reputation > 400000;

Tento index vezme uživatele, kteří mají více než 400 000 reputaci, a seřadí je podle DisplayName a Id. Může být jedinečný, protože (předpokládá se) sloupec ID je již jedinečný. Pokud něco podobného zkusíte na svém vlastním stole, možná si na to budete muset dát pozor.

V tuto chvíli je však indexu jedno, jakou má každý uživatel Reputaci – záleží mu pouze na tom, zda je Reputace dostatečně vysoká, aby byla v indexu nebo ne. Pokud se reputace uživatele aktualizuje a překročí prahovou hodnotu, do indexu se vloží DisplayName a Id uživatele. Pokud klesne pod, bude odstraněn z indexu. Je to jako mít samostatný stůl pro high rollery, až na to, že lidi do tohoto stolu dostaneme zvýšením jejich hodnoty reputace nad 400k práh v podkladové tabulce. Může to udělat, aniž by musel skutečně ukládat samotnou hodnotu Reputation.

Pokud tedy nyní chceme najít lidi, kteří mají práh vyšší než 450 000, v tomto indexu chybí některé informace.

Jistě, mohli bychom s jistotou říci, že každý, koho najdeme, je v tomto indexu – ale index sám o sobě neobsahuje dostatek informací, aby mohl dále filtrovat podle pověsti. Kdybych vám řekl, že mám abecední seznam filmů oceněných Oscarem za nejlepší film z 90. let (Americká krása, Statečné srdce, Tanec s vlky, Anglický pacient, Forrest Gump, Schindlerův seznam, Zamilovaný Shakespeare, Mlčení jehňátek, Titanic, Nesmiřitelné) , pak vás mohu ujistit, že vítězové za roky 1994-1996 by byli podmnožinou těch, ale nemohu na otázku odpovědět, aniž bych nejprve získal nějaké další informace.

Můj filtrovaný index by byl samozřejmě užitečnější, kdybych zahrnul rok, a potenciálně ještě více, pokud by rok byl klíčovým sloupcem, protože můj nový dotaz chce najít ty za roky 1994-1996. Ale pravděpodobně jsem navrhl tento rejstřík kolem dotazu, abych uvedl všechny filmy z 90. let v abecedním pořadí. Tento dotaz se nestará o to, jaký je skutečný rok, pouze zda je to v 90. letech nebo ne, a já ani nemusím vracet rok – jen název – abych mohl naskenovat svůj filtrovaný index a získat výsledky. Pro tento dotaz ani nemusím měnit pořadí výsledků nebo hledat výchozí bod – můj index je opravdu perfektní.

Praktičtějším příkladem, kdy se nestaráte o hodnotu sloupce ve filtru, je stav on, například:

WHERE IsActive = 1

Často vidím kód, který přesouvá data z jedné tabulky do druhé, když řádky přestanou být „aktivní“. Lidé nechtějí, aby jim staré řádky zaplňovaly tabulku, a uvědomují si, že jejich „horká“ data jsou jen malou podmnožinou všech jejich dat. Přesouvají data o chlazení do archivní tabulky, přičemž jejich aktivní tabulka zůstává malá.

Filtrovaný index to může udělat za vás. V zákulisí. Jakmile aktualizujete řádek a změníte sloupec IsActive na něco jiného než 1. Pokud vám záleží pouze na tom, abyste měli aktivní data ve většině vašich indexů, pak jsou filtrované indexy ideální. Dokonce to vrátí řádky zpět do indexů, pokud se hodnota IsActive změní zpět na 1.

K dosažení tohoto cíle však nemusíte uvádět IsActive do seznamu INCLUDE. Proč byste chtěli ukládat hodnotu – už víte, jaká je hodnota – je to 1! Pokud nepožadujete vrácení hodnoty, neměli byste ji potřebovat. A proč byste vraceli hodnotu, když už víte, že odpověď je 1, že?! Až na to je frustrující to, že statistiky, na které Erik ve svém příspěvku odkazuje, budou využívat výhod umístění v seznamu INCLUDE. Nepotřebujete jej pro dotaz, ale měli byste jej zahrnout do statistik.

Pojďme se zamyslet nad tím, co musí udělat Optimalizátor dotazů, aby zjistil užitečnost indexu.

Než bude moci udělat mnoho, musí zvážit, zda je index kandidátem. Nemá smysl používat index, pokud neobsahuje všechny řádky, které by mohly být potřeba – pokud nemáme efektivní způsob, jak získat zbytek. Pokud chci filmy z let 1985-1995, pak můj index filmů z 90. let je docela k ničemu. Ale na roky 1994-1996 to možná není špatné.

V tuto chvíli, stejně jako u všech úvah o indexu, musím přemýšlet o tom, zda to dostatečně pomůže k nalezení dat a jejich zařazení do pořadí, které pomůže provést zbytek dotazu (možná pro spojení Merge Join, Stream Aggregate, uspokojující OBJEDNÁVKA, nebo z různých jiných důvodů). Pokud se můj filtr dotazu přesně shoduje s filtrem indexu, nemusím dále filtrovat – stačí použít index. To zní skvěle, ale pokud se to přesně neshoduje, pokud je můj filtr dotazů přísnější než filtr indexu (jako můj příklad 1994–1996 nebo Erikův 450 000), budu muset mít tyto hodnoty roku nebo hodnoty reputace zkontrolovat – doufejme, že je získám buď z ZAHRNUTÝCH na úrovni listu nebo někde v mých klíčových sloupcích. Pokud nejsou v indexu, budu muset provést vyhledávání pro každý řádek v mém filtrovaném indexu (a v ideálním případě mít představu o tom, kolikrát bude moje vyhledávání voláno, což jsou statistiky, které Erik chce sloupec zahrnutý pro).

V ideálním případě je každý index, který plánuji použít, seřazen správně (pomocí klíčů), ZAHRNUJE všechny sloupce, které potřebuji vrátit, a je předfiltrován pouze na řádky, které potřebuji. To by byl perfektní index a můj plán provádění bude skenování.

Správně, SKENOVÁNÍ. Ne hledání, ale skenování. Začne na první stránce mého rejstříku a bude mi dávat řádky, dokud jich nebudu mít tolik, kolik potřebuji, nebo dokud nebudou žádné další řádky k vrácení. Žádné přeskakovat, neřadit je – jen mi dávat řádky v pořadí.

Seek by naznačoval, že nepotřebuji celý index, což znamená, že plýtvám zdroji při údržbě této části indexu, a abych se mohl dotazovat, musím najít výchozí bod a průběžně kontrolovat řádky, abych zjistil, zda jsem trefit konec nebo ne. Pokud má moje skenování predikát, pak jistě, musím procházet (a testovat) více dat, než potřebuji, ale pokud jsou mé indexové filtry dokonalé, pak by to měl nástroj Query Optimizer rozpoznat a nemusel by tyto kontroly provádět. .

Poslední myšlenky

INCLUDE nejsou pro filtrované indexy kritické. Jsou užitečné pro zajištění snadného přístupu ke sloupcům, které mohou být užitečné pro váš dotaz, a pokud náhodou zužujete obsah filtrovaného indexu libovolným sloupcem, ať už je ve filtru zmíněn, nebo ne, měli byste zvážit umístění tohoto sloupce směs. Ale v tu chvíli byste se měli ptát, zda je filtr vašeho indexu správný, co dalšího byste měli mít ve svém seznamu INCLUDE a dokonce jaké by měly být klíčové sloupce. Erikovy dotazy nehrály dobře, protože potřeboval informace, které nebyly v indexu, i když se o sloupci ve filtru zmínil. Našel také dobré využití pro statistiky, a přesto bych vám doporučil zahrnout sloupce filtru z tohoto důvodu. Ale jejich umístění do INCLUDE jim nedovolí, aby najednou začali hledat Seek, protože takhle nefunguje žádný index, ať už je filtrovaný nebo ne.

Chci, abyste, čtenáři, rozuměl filtrovaným indexům opravdu dobře. Jsou neuvěřitelně užitečné, a když si je začnete představovat jako tabulky v jejich vlastních právech, mohou se stát součástí vašeho celkového návrhu databáze. Jsou také důvodem, proč vždy používat nastavení ANSI_NULL a QUOTED_IDENTIFIER, protože pokud tato nastavení nejsou ZAPNUTÁ, budete dostávat chyby z filtrovaného indexu, ale doufejme, že už se ujistíte, že jsou i tak vždy zapnutá.

A ty filmy byly Forrest Gump, Statečné srdce a Anglický pacient.

@rob_farley


  1. Načítání data na serveru SQL, CURRENT_TIMESTAMP vs GetDate()

  2. Poddotaz SQL Server vrátil více než 1 hodnotu. To není povoleno, pokud poddotaz následuje =, !=, <, <=,>,>=

  3. Více plánů pro identický dotaz

  4. MySQL – podmíněná omezení cizích klíčů