Toto je třináctý a poslední díl série o tabulkových výrazech. Tento měsíc pokračuji v diskusi, kterou jsem začal minulý měsíc, o funkcích s inline tabulkou (iTVF).
Minulý měsíc jsem vysvětlil, že když SQL Server vloží iTVF, které jsou dotazovány pomocí konstant jako vstupů, použije ve výchozím nastavení optimalizaci vkládání parametrů. Vkládání parametrů znamená, že SQL Server nahradí odkazy na parametry v dotazu doslovnými hodnotami konstant z aktuálního provádění a poté se kód s konstantami optimalizuje. Tento proces umožňuje zjednodušení, která mohou vést k optimálnějším plánům dotazů. Tento měsíc toto téma rozpracuji a pokryjem konkrétní případy takových zjednodušení, jako je neustálé skládání a dynamické filtrování a řazení. Pokud si potřebujete osvěžit optimalizaci vkládání parametrů, projděte si článek z minulého měsíce a také vynikající článek Paula Whitea Parameter Sniffing, Embedding a RECOMPILE Options.
Ve svých příkladech použiji ukázkovou databázi s názvem TSQLV5. Skript, který jej vytváří a naplňuje, najdete zde a jeho ER diagram zde.
Konstantní skládání
Během raných fází zpracování dotazu SQL Server vyhodnocuje určité výrazy obsahující konstanty a skládá je do výsledných konstant. Například výraz 40 + 2 lze složit na konstantu 42. Pravidla pro skládací a neskládací výrazy naleznete zde v části „Vyhodnocení konstantního skládání a výrazu.“
Co je zajímavé s ohledem na iTVF je to, že díky optimalizaci vkládání parametrů mohou dotazy zahrnující iTVF, kde předáváte konstanty jako vstupy, za správných okolností těžit z neustálého skládání. Znalost pravidel pro skládací a neskládací výrazy může ovlivnit způsob, jakým implementujete vaše iTVF. V některých případech můžete použitím velmi jemných změn ve výrazech povolit optimálnější plány s lepším využitím indexování.
Jako příklad zvažte následující implementaci iTVF s názvem Sales.MyOrders:
POUŽÍVEJTE TSQLV5;GO VYTVOŘIT NEBO ZMĚNIT FUNKCI Sales.MyOrders ( @add AS INT, @subtract AS INT )RETURNS TABLEASRETURN SELECT orderid + @add - @subtract AS myorderid, orderdate, custid, empid FROM Sales.Orders;GOZadejte následující dotaz týkající se iTVF (budu ho označovat jako dotaz 1):
SELECT myorderid, orderdate, custid, empidFROM Sales.MyOrders(1, 10248)ORDER BY myorderid;Plán pro Dotaz 1 je znázorněn na obrázku 1.
Obrázek 1:Plán pro dotaz 1
Clusterový index PK_Orders je definován s orderid jako klíčem. Pokud by zde po vložení parametru probíhalo konstantní skládání, uspořádaný výraz orderid + 1 – 10248 by byl složen na orderid – 10247. Tento výraz by byl považován za výraz zachovávající pořadí s ohledem na orderid, a jako takový by umožnil optimalizátor spoléhat na pořadí indexů. Bohužel tomu tak není, jak je zřejmé z explicitního operátora řazení v plánu. Tak, co se stalo?
Konstantní skládací pravidla jsou vychytralá. Výraz sloupec1 + konstanta1 – konstanta2 je vyhodnocen zleva doprava pro účely konstantního skládání. První část, sloupec1 + konstanta1, není složena. Nazvěme tento výraz 1. Další část, která je vyhodnocována, je považována za výraz1 – konstanta2, která také není složena. Bez skládání se výraz ve tvaru sloupec1 + konstanta1 – konstanta2 nepovažuje za zachování pořadí s ohledem na sloupec1, a proto nelze spoléhat na řazení indexů, i když máte podpůrný index na sloupci1. Podobně výraz konstanta1 + sloupec1 – konstanta2 není konstanta skládací. Výraz konstanta1 – konstanta2 + sloupec1 je však skládací. Přesněji řečeno, první část konstanta1 – konstanta2 je složena do jediné konstanty (říkejme jí konstanta3), výsledkem je výraz konstanta3 + sloupec1. Tento výraz je považován za výraz zachovávající pořadí s ohledem na sloupec1. Pokud se tedy ujistíte, že výraz zapisujete pomocí posledního formuláře, můžete optimalizátoru povolit, aby se spoléhal na řazení indexů.
Zvažte následující dotazy (budu je označovat jako Dotaz 2, Dotaz 3 a Dotaz 4) a než se podíváte na plány dotazů, zjistěte, zda můžete říci, které budou zahrnovat explicitní řazení v plánu a které ne:
-- Dotaz 2SELECT orderid + 1 - 10248 AS myorderid, orderdate, custid, empidFROM Sales.OrdersORDER BY myorderid; -- Dotaz 3SELECT 1 + orderid - 10248 AS myorderid, orderdate, custid, empidFROM Sales.OrdersORDER BY myorderid; -- Dotaz 4SELECT 1 - 10248 + orderid AS myorderid, orderdate, custid, empidFROM Sales.OrdersORDER BY myorderid;Nyní prozkoumejte plány pro tyto dotazy, jak je znázorněno na obrázku 2.
Obrázek 2:Plány pro Dotaz 2, Dotaz 3 a Dotaz 4
Prozkoumejte operátory Compute Scalar ve třech plánech. Pouze u plánu pro Dotaz 4 došlo k neustálému skládání, což vedlo k výrazu řazení, který je považován za zachovávající pořadí s ohledem na orderid, čímž se vyhýbá explicitnímu řazení.
Pochopíte-li tento aspekt neustálého skládání, můžete iTVF snadno opravit změnou výrazu orderid + @add – @subtract na @add – @subtract + orderid, například takto:
VYTVOŘIT NEBO ZMĚNIT FUNKCI Sales.MyOrders ( @add AS INT, @subtract AS INT )RETURNS TABLEASRETURN SELECT @add - @subtract + orderid AS myorderid, orderdate, custid, empid FROM Sales.Orders;GODotaz na funkci znovu (budu to označovat jako Dotaz 5):
SELECT myorderid, orderdate, custid, empidFROM Sales.MyOrders(1, 10248)ORDER BY myorderid;Plán pro tento dotaz je znázorněn na obrázku 3.
Obrázek 3:Plán pro dotaz 5
Jak můžete vidět, tentokrát se dotaz neustále skládal a optimalizátor se mohl spolehnout na řazení indexů, čímž se vyhnul explicitnímu řazení.
K demonstraci této optimalizační techniky jsem použil jednoduchý příklad a jako taková se může zdát trochu vykonstruovaná. Praktickou aplikaci této techniky najdete v článku Řešení výzev generátoru číselných řad – 1. část.
Dynamické filtrování/řazení
Minulý měsíc jsem se zabýval rozdílem mezi tím, jak SQL Server optimalizuje dotaz v iTVF oproti stejnému dotazu v uložené proceduře. SQL Server obvykle použije optimalizaci vkládání parametrů ve výchozím nastavení pro dotaz zahrnující iTVF s konstantami jako vstupy, ale optimalizuje parametrizovanou formu dotazu v uložené proceduře. Pokud však přidáte OPTION(RECOMPILE) do dotazu v uložené proceduře, SQL Server obvykle použije optimalizaci vkládání parametrů i v tomto případě. Mezi výhody v případě iTVF patří skutečnost, že jej můžete zapojit do dotazu, a pokud předáváte opakující se konstantní vstupy, existuje potenciál pro opětovné použití dříve uloženého plánu. S uloženou procedurou ji nemůžete zahrnout do dotazu, a pokud přidáte OPTION(RECOMPILE), abyste získali optimalizaci vkládání parametrů, neexistuje možnost opětovného použití plánu. Uložená procedura umožňuje mnohem větší flexibilitu, pokud jde o prvky kódu, které můžete použít.
Podívejme se, jak se to všechno odehrává v klasické úloze vkládání parametrů a objednávání. Následuje zjednodušená uložená procedura, která používá dynamické filtrování a řazení podobné tomu, které použil Paul ve svém článku:
VYTVOŘIT NEBO ZMĚNIT POSTUP HR.GetEmpsP @lastnamepattern AS NVARCHAR(50), @sort AS TINYINTASSET NOCOUNT ON; SELECT empid, firstname, lastnameFROM HR.EmployeesWHERE lastname LIKE @lastnamepattern OR @lastnamepattern JE NULLORDER BY CASE WHEN @sort =1 THEN empid END, CASE WHEN @sort =2 THEN firstname END, Sort =CASE WHEN;Všimněte si, že aktuální implementace uložené procedury nezahrnuje OPTION(RECOMPILE) v dotazu.
Zvažte následující provedení uložené procedury:
EXEC HR.GetEmpsP @lastnamepattern =N'D%', @sort =3;Plán tohoto provedení je znázorněn na obrázku 4.
Obrázek 4:Plán postupu HR.GetEmpsP
Ve sloupci příjmení je definován index. Teoreticky by se současnými vstupy mohl být index prospěšný jak pro potřeby filtrování (s hledáním), tak pro potřeby řazení (s uspořádaným:true range scan) potřeby dotazu. Protože však ve výchozím nastavení SQL Server optimalizuje parametrizovanou formu dotazu a nepoužívá vkládání parametrů, neuplatňuje zjednodušení potřebná k tomu, aby bylo možné těžit z indexu pro účely filtrování i řazení. Plán je tedy znovu použitelný, ale není optimální.
Chcete-li vidět, jak se věci mění s optimalizací vkládání parametrů, změňte dotaz uložené procedury přidáním OPTION(RECOMPILE), například takto:
VYTVOŘIT NEBO ZMĚNIT POSTUP HR.GetEmpsP @lastnamepattern AS NVARCHAR(50), @sort AS TINYINTASSET NOCOUNT ON; SELECT empid, firstname, lastnameFROM HR.EmployeesWHERE lastname LIKE @lastnamepattern OR @lastnamepattern JE NULLORDER BY CASE WHEN @sort =1 THEN empid END, CASE WHEN @sort =2 THEN firstname END,sort lastname WHEN, CASECOMPIN THEN );GOProveďte uloženou proceduru znovu se stejnými vstupy, které jste použili dříve:
EXEC HR.GetEmpsP @lastnamepattern =N'D%', @sort =3;Plán tohoto provedení je znázorněn na obrázku 5.
Obrázek 5:Plán postupu HR.GetEmpsP s OPTION(RECOMPILE)
Jak můžete vidět, díky optimalizaci vkládání parametrů byl SQL Server schopen zjednodušit predikát filtru na sargable predikát příjmení LIKE N'D%' a objednávkový seznam na NULL, NULL, příjmení. Oba prvky by mohly těžit z indexu na příjmení, a proto plán ukazuje hledání v indexu a žádné explicitní řazení.
Teoreticky očekáváte, že budete schopni získat podobné zjednodušení, pokud implementujete dotaz v iTVF, a tedy podobné výhody optimalizace, ale se schopností znovu použít plány uložené v mezipaměti, když jsou znovu použity stejné vstupní hodnoty. Takže zkusíme…
Zde je pokus o implementaci stejného dotazu v iTVF (tento kód zatím nespouštějte):
VYTVOŘIT NEBO ZMĚNIT FUNKCI HR.GetEmpsF( @lastnamepattern AS NVARCHAR(50), @sort AS TINYINT)RETURNS TABLEASRETURN SELECT empid, jméno, příjmení FROM HR.Employees WHERE příjmení LIKE @lastnameCpattern WDERHENH @sort =1 THEN empid END, CASE WHEN @sort =2 THEN firstname END, CASE WHEN @sort =3 THEN lastname END;GONež se pokusíte spustit tento kód, vidíte problém s touto implementací? Pamatujte, že na začátku této série jsem vysvětlil, že tabulkový výraz je tabulka. Tělo tabulky je množina (nebo více množina) řádků a jako taková nemá žádné pořadí. Proto normálně dotaz použitý jako tabulkový výraz nemůže mít klauzuli ORDER BY. Pokud se pokusíte spustit tento kód, zobrazí se následující chyba:
Msg 1033, Level 15, State 1, Procedure GetEmps, Line 16 [Batch Start Line 128]
Klauzule ORDER BY je neplatná v pohledech, vložených funkcích, odvozených tabulkách, poddotazech a běžných tabulkových výrazech, pokud není TOP, OFFSET nebo FOR XML je také specifikován.Jistě, jak chyba říká, SQL Server udělá výjimku, pokud použijete filtrovací prvek jako TOP nebo OFFSET-FETCH, který se opírá o klauzuli ORDER BY k definování aspektu řazení filtru. Ale i když díky této výjimce zahrnete klauzuli ORDER BY do vnitřního dotazu, stále nezískáte záruku na pořadí výsledku ve vnějším dotazu proti tabulkovému výrazu, pokud nemá vlastní klauzuli ORDER BY .
Pokud stále chcete implementovat dotaz v iTVF, můžete nechat vnitřní dotaz zpracovat část dynamického filtrování, ale ne dynamické řazení, jako je to takto:
VYTVOŘTE NEBO ZMĚŇTE FUNKCI HR.GetEmpsF( @lastnamepattern AS NVARCHAR(50))RETURNS TABLEASRETURN SELECT empid, jméno, příjmení FROM HR.Employees KDE příjmení LIKE @lastnamepattern NEBO @lastnamepattern JE @NULL;GOSamozřejmě můžete nechat vnější dotaz zpracovat jakoukoli konkrétní potřebu objednávky, jako v následujícím kódu (budu to označovat jako dotaz 6):
SELECT empid, firstname, lastnameFROM HR.GetEmpsF(N'D%')ORDER BY lastname;Plán pro tento dotaz je znázorněn na obrázku 6.
Obrázek 6:Plán pro dotaz 6
Díky vkládání a vkládání parametrů je plán podobný plánu uvedenému dříve pro dotaz uložené procedury na obrázku 5. Plán efektivně spoléhá na index jak pro účely filtrování, tak pro účely řazení. Nezískáte však flexibilitu vstupu dynamického řazení, jako jste měli s uloženou procedurou. Musíte být explicitní s řazením v klauzuli ORDER BY v dotazu na funkci.
Následující příklad obsahuje dotaz na funkci bez filtrování a bez požadavků na objednávání (budu to označovat jako Dotaz 7):
SELECT empid, firstname, lastnameFROM HR.GetEmpsF(NULL);Plán pro tento dotaz je znázorněn na obrázku 7.
Obrázek 7:Plán pro dotaz 7
Po vložení a vložení parametrů je dotaz zjednodušený tak, aby neměl žádný predikát filtru a řazení, a optimalizuje se pomocí úplného neuspořádaného skenování seskupeného indexu.
Nakonec zadejte dotaz na funkci s N'D%' jako vstupní vzor filtrování příjmení a seřaďte výsledek podle sloupce křestního jména (budu to označovat jako Dotaz 8):
SELECT empid, firstname, lastnameFROM HR.GetEmpsF(N'D%')ORDER BY firstname;Plán pro tento dotaz je znázorněn na obrázku 8.
Obrázek 8:Plán pro dotaz 8
Po zjednodušení dotaz zahrnuje pouze filtrovací predikát příjmení LIKE N'D%' a objednávkový prvek jméno. Tentokrát se optimalizátor rozhodne použít neuspořádané prohledávání seskupeného indexu se zbytkovým predikátem příjmení LIKE N'D%', po kterém následuje explicitní řazení. Rozhodl se nepoužít hledání v indexu na příjmení, protože index není krycí, tabulka je tak malá a řazení indexu není výhodné pro aktuální potřeby řazení dotazů. Ve sloupci křestního jména také není definován žádný index, takže musí být stejně použito explicitní řazení.
Závěr
Výchozí optimalizace vkládání parametrů iTVF může také vést k neustálému skládání, což umožňuje optimálnější plány. Musíte však dbát na konstantní pravidla skládání, abyste určili, jak nejlépe formulovat své výrazy.
Implementace logiky v iTVF má výhody a nevýhody ve srovnání s implementací logiky v uložené proceduře. Pokud vás nezajímá optimalizace vkládání parametrů, výchozí optimalizace parametrizovaných dotazů uložených procedur může vést k optimálnějšímu ukládání plánu do mezipaměti a chování při opětovném použití. V případech, kdy máte zájem o optimalizaci vkládání parametrů, obvykle ji získáte ve výchozím nastavení s iTVF. Chcete-li získat tuto optimalizaci s uloženými procedurami, musíte přidat možnost dotazu PŘEKOMPILOVAT, ale pak už nebudete mít možnost znovu použít plán. Alespoň s iTVF můžete získat opětovné použití plánu za předpokladu, že se opakují stejné hodnoty parametrů. Pak opět máte menší flexibilitu s prvky dotazu, které můžete použít v iTVF; například nemáte povoleno mít klauzuli ORDER BY prezentace.
Zpět k celé sérii o tabulkových výrazech považuji toto téma za velmi důležité pro uživatele databáze. Kompletnější série zahrnuje podsérie na generátoru číselných řad, který je implementován jako iTVF. Celkem série obsahuje následujících 19 dílů:
- Základy tabulkových výrazů, část 1
- Základy tabulkových výrazů, Část 2 – Odvozené tabulky, logické úvahy
- Základy tabulkových výrazů, Část 3 – Odvozené tabulky, úvahy o optimalizaci
- Základy tabulkových výrazů, Část 4 – Odvozené tabulky, úvahy o optimalizaci, pokračování
- Základy tabulkových výrazů, Část 5 – CTE, logické úvahy
- Základy tabulkových výrazů, Část 6 – Rekurzivní CTE
- Základy tabulkových výrazů, Část 7 – CTE, úvahy o optimalizaci
- Základy tabulkových výrazů, část 8 – CTE, úvahy o optimalizaci pokračují
- Základy tabulkových výrazů, část 9 – Zobrazení, v porovnání s odvozenými tabulkami a CTE
- Základy tabulkových výrazů, Část 10 – Zobrazení, SELECT * a změny DDL
- Základy tabulkových výrazů, Část 11 – Pohledy, úvahy o úpravách
- Základy tabulkových výrazů, Část 12 – Vložené funkce s hodnotou tabulky
- Základy tabulkových výrazů, část 13 – Vložené funkce s hodnotou tabulky, pokračování
- Výzva je připravena! Výzva komunity k vytvoření nejrychlejšího generátoru číselných řad
- Řešení problémů s generátorem číselné řady – Část 1
- Řešení problémů s generátorem číselné řady – část 2
- Řešení problémů s generátorem číselné řady – část 3
- Řešení problémů s generátorem číselné řady – část 4
- Řešení problémů s generátorem číselné řady – část 5