sql >> Databáze >  >> RDS >> Sqlserver

SQL Server Internals:Problematic Operators Pt. II – Hašování

Toto je součástí série SQL Server Internals Problematic Operators. Chcete-li si přečíst první příspěvek, klikněte sem.

SQL Server existuje více než 30 let a já pracuji se serverem SQL Server téměř stejně dlouho. V průběhu let (a desetiletí!) a verzí tohoto neuvěřitelného produktu jsem viděl mnoho změn. V těchto příspěvcích se s vámi podělím o to, jak se dívám na některé funkce nebo aspekty SQL Serveru, někdy spolu s trochou historické perspektivy.

Minule jsem mluvil o operaci skenování v plánu dotazů SQL Server jako o potenciálně problematickém operátoru v diagnostice SQL Serveru. Ačkoli se skenování často používá pouze proto, že neexistuje žádný užitečný index, jsou chvíle, kdy je skenování ve skutečnosti lepší volbou než operace hledání indexu.

V tomto článku vám řeknu o další rodině operátorů, která je občas vnímána jako problematická:hašování. Hašování je velmi známý algoritmus zpracování dat, který existuje již mnoho desetiletí. Studoval jsem to v hodinách datových struktur, když jsem poprvé studoval informatiku na univerzitě. Pokud byste chtěli získat základní informace o hašování a hašovacích funkcích, můžete se podívat na tento článek na Wikipedii. SQL Server však nepřidal hašování do svého repertoáru možností zpracování dotazů až do SQL Server 7. (Namísto toho zmíním, že SQL Server používal hašování v některých svých vlastních interních vyhledávacích algoritmech. Jak uvádí článek na Wikipedii , hašování používá speciální funkci k mapování dat libovolné velikosti na data pevné velikosti. SQL používal hašování jako vyhledávací techniku ​​k mapování každé stránky z databáze libovolné velikosti do vyrovnávací paměti v paměti, která má pevnou velikost. , dříve existovala možnost sp_configure nazývané ‚hash buckets‘, což vám umožnilo řídit počet segmentů používaných pro hašování databázových stránek do vyrovnávacích pamětí.)

Co je hašování?

Hašování je technika vyhledávání, která nevyžaduje uspořádání dat. SQL Server jej může použít pro operace JOIN, agregační operace (DISTINCT nebo GROUP BY) nebo UNION operace. Tyto tři operace mají společné to, že během provádění dotazovací stroj hledá odpovídající hodnoty. V JOIN chceme najít řádky v jedné tabulce (nebo sadě řádků), které mají odpovídající hodnoty s řádky v jiné. (A ano, jsem si vědom spojení, která neporovnávají řádky na základě rovnosti, ale tato nerovnoprávná spojení jsou pro tuto diskuzi irelevantní.) Pro GROUP BY najdeme odpovídající hodnoty k zahrnutí do stejné skupiny a pro UNION a DISTINCT, hledáme odpovídající hodnoty, abychom je vyloučili. (Ano, vím, že UNION ALL je výjimkou.)

Před SQL Server 7 jediným způsobem, jak tyto operace mohly snadno najít odpovídající hodnoty, bylo řazení dat. Pokud tedy neexistuje žádný existující index, který by uchovával data v seřazeném pořadí, plán dotazů by do plánu přidal operaci SORT. Hašování uspořádá vaše data pro efektivní vyhledávání tím, že všechny řádky, které mají stejný výsledek z interní hašovací funkce, vloží do stejného „hash bucketu“.

Pro podrobnější vysvětlení operace hash JOIN na SQL Serveru, včetně diagramů, se podívejte na tento blogový příspěvek od SQL Shack.

Jakmile se hašování stalo možností, SQL Server zcela neopomíjel možnost třídění dat před připojením nebo agregací, ale stal se pouze možností, kterou by měl optimalizátor zvážit. Obecně však platí, že pokud se pokoušíte připojit, agregovat nebo provést UNION na netříděných datech, optimalizátor obvykle zvolí operaci hash. Tolik lidí předpokládá, že HASH JOIN (nebo jiná HASH operace) v plánu znamená, že nemáte vhodné indexy a že byste měli vytvořit vhodné indexy, abyste se vyhnuli hašovací operaci.

Podívejme se na příklad. Nejprve vytvořím dvě neindexované tabulky.

USE AdventureWorks2016 GO DROP TABLE IF EXISTS Details;

GO

SELECT * INTO Details FROM Sales.SalesOrderDetail;

GO

DROP TABLE IF EXISTS Headers;

GO

SELECT * INTO Headers FROM Sales.SalesOrderHeader;

GO

Now, I’ll join these two tables together and filter the rows in the Details table:

SELECT *

FROM Details d JOIN Headers h

ON d.SalesOrderID = h.SalesOrderID

WHERE SalesOrderDetailID < 100;

Zdá se, že Quest Spotlight Tuning Pack nenaznačuje, že spojení hash je problém. Zvýrazňuje pouze dva skeny tabulky.

Návrhy doporučují vytvořit pro každou tabulku index, který bude obsahovat každý jeden neklíčový sloupec jako sloupec INCLUDED. Málokdy tato doporučení přijímám (jak jsem zmínil ve svém předchozím příspěvku). Vytvořím pouze index na Podrobnosti tabulky, ve sloupci spojení a nemají žádné zahrnuté sloupce.

CREATE INDEX Header_index on Headers(SalesOrderID);

Jakmile je tento index vytvořen, HASH JOIN zmizí. Index třídí data v Hlavičkách tabulky a umožňuje serveru SQL najít odpovídající řádky ve vnitřní tabulce pomocí třídicí sekvence indexu. Nyní je nejdražší částí plánu skenování vnějšího stolu (Podrobnosti ), což by bylo možné snížit vytvořením indexu na SalesOrderID sloupec v této tabulce. Nechám to jako cvičení pro čtenáře.

Plán s HASH JOIN však není vždy špatná věc. Alternativním operátorem (kromě speciálních případů) je NESTED LOOPS JOIN, což je obvykle volba, pokud existují dobré indexy. Operace vnořených smyček však vyžaduje vícenásobné prohledávání vnitřní tabulky. Následující pseudokód ukazuje algoritmus spojení vnořených smyček:

for each row R1 in the outer table

     for each row R2 in the inner table

         if R1 joins with R2

             return (R1, R2)

Jak název napovídá, NESTED LOOP JOIN se provádí jako vnořená smyčka. Prohledávání vnitřní tabulky bude obvykle provedeno vícekrát, jednou pro každý kvalifikační řádek ve vnější tabulce. I když se kvalifikuje jen několik procent řádků, pokud je tabulka velmi velká (možná ve stovkách milionů, miliard nebo řádků), bude k přečtení spousta řádků. V systému, který je vázán na I/O, mohou být tyto miliony nebo miliardy čtení skutečným úzkým hrdlem.

Na druhé straně HASH JOIN neprovádí vícenásobné čtení žádné tabulky. Jednou přečte vnější tabulku, aby vytvořila segmenty hash, a pak jednou přečte vnitřní tabulku, přičemž zkontroluje, zda se v polích hash nachází, zda existuje odpovídající řádek. Máme horní limit jednoho průchodu každým stolem. Ano, k výpočtu hashovací funkce a správě obsahu bucketů jsou potřeba prostředky CPU. K uložení hašovaných informací jsou potřeba paměťové zdroje. Ale pokud máte I/O vázaný systém, můžete mít paměť a zdroje CPU nazbyt. HASH JOIN může být rozumnou volbou pro optimalizátor v těchto situacích, kdy jsou vaše I/O zdroje omezené a připojujete velmi velké tabulky.

Zde je pseudokód pro algoritmus spojení hash:

for each row R1 in the build table

  begin

     calculate hash value on R1 join key(s)

     insert R1 into the appropriate hash bucket

  end

for each row R2 in the probe table

  begin

     calculate hash value on R2 join key(s)

     for each row R1 in the corresponding hash bucket

         if R1 joins with R2

         output (R1, R2)

  end

Jak již bylo zmíněno dříve, hash lze také použít pro agregační (stejně jako UNION) operace. Opět platí, že pokud existuje užitečný index, který již má data seřazená, seskupování dat lze provést velmi efektivně. Existuje však také mnoho situací, kdy hash není vůbec špatný operátor. Zvažte dotaz jako je následující, který seskupuje data v části Podrobnosti tabulky (vytvořené výše) pomocí ProductID sloupec. Tabulka obsahuje 121 317 řádků a pouze 266 různých ID produktu hodnoty.

SELECT ProductID, count(*)

FROM Details

GROUP BY ProductID;

GO

Použití hašovacích operací

Chcete-li použít hašování, SQL Server musí vytvořit a udržovat pouze 266 segmentů, což není mnoho. Ve skutečnosti Quest Spotlight Tuning Pack nenaznačuje, že by s tímto dotazem byly nějaké problémy.

Ano, musí provést skenování tabulky, ale to proto, že musíme prozkoumat každý řádek v tabulce a víme, že skenování není vždy špatné. Index by pomohl pouze s předtříděním dat, ale použití agregace hash pro tak malý počet skupin bude stále obvykle poskytovat přiměřený výkon, i když není k dispozici žádný užitečný index.

Stejně jako skenování tabulek jsou hašovací operace často považovány za „špatné“ operátory, které je třeba mít v plánu. Existují případy, kdy můžete výrazně zlepšit výkon přidáním užitečných indexů k odstranění hašovacích operací, ale to není vždy pravda. A pokud se snažíte omezit počet indexů v tabulkách, které jsou intenzivně aktualizovány, měli byste si být vědomi toho, že hašovací operace nejsou vždy něco, co je třeba ‚opravit‘, takže ponechání dotazu na použití haše může být rozumná věc. dělat. Navíc pro určité dotazy na velké tabulky běžící na systémech vázaných na I/O může hašování ve skutečnosti poskytnout lepší výkon než alternativní algoritmy kvůli omezenému počtu čtení, která je třeba provést. Jediný způsob, jak to zjistit s jistotou, je otestovat různé možnosti na vašem systému s vašimi dotazy a vašimi daty.

V následujícím příspěvku v této sérii vám řeknu o dalších problematických operátorech, kteří se mohou objevit ve vašich plánech dotazů, takže se sem brzy vraťte!


  1. 11 funkcí pro získání dne, měsíce a roku z data v MariaDB

  2. Příklady COS() v SQL Server

  3. Jak svázat parametry s nezpracovaným dotazem DB v Laravelu, který se používá na modelu?

  4. Vyberte TOP X (nebo dolní) procento pro číselné hodnoty v MySQL