Jedním z dostupných algoritmů pro spojení dvou tabulek na serveru SQL Server jsou vnořené smyčky. Spojení vnořených smyček používá jeden vstup spojení jako vnější vstupní tabulku a jeden jako vnitřní vstupní tabulku. Vnější smyčka iteruje vnější vstupní tabulku řádek po řádku. Vnitřní smyčka, spuštěná pro každý vnější řádek, hledá odpovídající řádky ve vnitřní vstupní tabulce.
Toto se nazývá naivní spojení vnořených smyček.
Pokud máte index podmínek spojení ve vnitřní vstupní tabulce, není nutné provádět vnitřní smyčku pro každý řádek vnější tabulky. Místo toho můžete předat hodnotu z externí tabulky jako argument hledání a propojit všechny vrácené řádky vnitřní tabulky s řádky z vnější tabulky.
Vyhledávání podle vnitřní tabulky je náhodný přístup. SQL Server, počínaje verzí 2005, má dávkovou optimalizaci řazení (nepleťte si s operátorem Sort v dávkovém režimu pro indexy Columnstore). Účelem optimalizace je seřadit vyhledávací klíče z externí tabulky před získáním dat z interní tabulky. Náhodný přístup tedy bude sekvenční.
Plán provádění nezobrazuje operaci hromadného řazení jako samostatný operátor. Místo toho můžete vidět vlastnost Optimized=true v operátoru Nested Loops. Pokud by bylo možné vidět dávkové řazení jako samostatný operátor v plánu, vypadalo by to následovně:
V tomto pseudoplánu čteme data z neklastrovaného indexu ix_CustomerID v pořadí podle tohoto indexového klíče CustomerID. Potom musíme provést vyhledávání klíčů v seskupeném indexu, protože ix_CustomerID není krycí index. Vyhledávání klíčů je operace vyhledávání seskupeného indexového klíče – náhodný přístup. Aby bylo sekvenční, SQL Server může provádět dávkové řazení podle klíče clusteru indexu.
Chcete-li se dozvědět více o dávkovém třídění, přečtěte si můj článek Dávkové třídění a vnořené smyčky.
Tato optimalizace poskytuje velkou podporu s dostatečným počtem řádků. Více o výsledcích testů si můžete přečíst na blogu OPTIMIZED Nested Loops Joins, který vytvořil Craig Freedman, vývojář optimalizátoru.
Pokud je však skutečný počet řádků menší než očekávaný, pak dodatečné náklady na CPU na vytvoření tohoto řazení mohou skrýt jeho výhody, zvýšit spotřebu CPU a snížit jeho výkon.
Zvažte tento konkrétní příklad:
use tempdb; go -- create a test table (SalesOrderID - clustered PK) create table dbo.SalesOrder(SalesOrderID int identity primary key, CustomerID int not null, SomeData char(200) not null); go -- add test data with n as (select top(1000000) rn = row_number() over(order by (select null)) from sys.all_columns c1,sys.all_columns c2) insert dbo.SalesOrder(CustomerID, SomeData) select rn%500000, str(rn,100) from n; -- create a clustered index create index ix_c on dbo.Salesorder(CustomerID); go -- the batch sort optimization is enabled by default (Nested Loops: Optimized = true) select * from dbo.SalesOrder with(index(ix_c)) where CustomerID < 1000; -- disable it with the DISABLE_OPTIMIZED_NESTED_LOOP hint (Nested Loops: Optimized = false) select * from dbo.SalesOrder with(index(ix_c)) where CustomerID < 1000 option(use hint('DISABLE_OPTIMIZED_NESTED_LOOP')); go
Vrácený výsledek:
Rád bych upozornil na rozdílné pořadí řádků ve výstupu. Server vrací řádky v pořadí, v jakém je zpracovává, protože jsme explicitně neurčili ORDER BY. V prvním případě postupně čteme z indexu ix_c. Abychom však optimalizovali náhodné čtení z seskupeného indexu, filtrujeme řádky podle seskupeného indexového klíče SalesOrderID. Ve druhém případě nedochází k žádnému řazení a čtení se provádí v pořadí klíčů CustomerID neklastrovaného indexu ix_c.
Rozdíl od příznaku trasování 2340
Navzdory skutečnosti, že dokumentace specifikuje příznak trasování 2340 jako ekvivalent nápovědy DISABLE_OPTIMIZED_NESTED_LOOP, ve skutečnosti to není pravda.
Zvažte následující příklad, kde použiji nezdokumentovaný příkaz UPDATE STATISTICS … WITH PAGECOUNT k oklamání optimalizátoru tvrzením, že tabulka zabírá více stránek, než ve skutečnosti má. Pak se podívejte na tyto dotazy:
- Bez jakýchkoli nápověd (přidal jsem MAXDOP, abych zachoval jednoduchý plán);
- S nápovědou DISABLE_OPTIMIZED_NESTED_LOOP;
- S příznakem trasování 2340.
-- create a huge table update statistics dbo.SalesOrder with pagecount = 100000; go set showplan_xml on; go -- 1. without hints select * from dbo.SalesOrder with(index(ix_c)) where CustomerID < 1000000 option(maxdop 1); -- 2. hint select * from dbo.SalesOrder with(index(ix_c)) where CustomerID < 1000000 option(use hint('DISABLE_OPTIMIZED_NESTED_LOOP'), maxdop 1); -- 3. trace flag select * from dbo.SalesOrder with(index(ix_c)) where CustomerID < 1000000 option(querytraceon 2340, maxdop 1); go set showplan_xml off; go
V důsledku toho máme následující plány:
Vnořené smyčky mají ve všech třech plánech vlastnost Optimized =false. Faktem je, že zvětšením šířky tabulky zvyšujeme také náklady na přístup k datům. Když jsou náklady dostatečně vysoké, SQL Server může použít explicitní operátor řazení, nikoli implicitní operátor dávkového řazení. Můžeme to vidět v prvním plánu dotazů.
Ve druhém dotazu jsme použili nápovědu DISABLE_OPTIMIZED_NESTED_LOOP, která vypíná implicitní dávkové řazení. Odstraní však explicitní řazení pomocí samostatného operátora.
Ve třetím plánu můžeme vidět, že i přes přidání příznaku trasování 2340 operátor řazení existuje.
Rozdíl mezi nápovědou a příznakem je tedy následující:nápověda zakáže optimalizaci transformací náhodného přístupu na sériový v závislosti na tom, zda jej server implementuje pomocí implicitního dávkového řazení nebo pomocí samostatného operátoru řazení.
P.S. Plány mohou záviset na vybavení. Pokud se vám tedy nepodaří tyto dotazy spustit, zkuste zvětšit nebo zmenšit velikost sloupce SomeData char(200) v popisu tabulky dbo.SalesOrder.
Článek byl přeložen týmem Codingsight se svolením autora.