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

Jednoduchá parametrizace a triviální plány — 2. část

Datové typy parametrů

Jak bylo zmíněno v první části této série, jedním z důvodů, proč je lepší explicitně parametrizovat, je to, že máte plnou kontrolu nad datovými typy parametrů. Jednoduchá parametrizace má v této oblasti řadu zvláštností, které mohou mít za následek ukládání více parametrizovaných plánů do mezipaměti, než se očekávalo, nebo nalezení jiných výsledků ve srovnání s neparametrizovanou verzí.

Když SQL Server použije jednoduchou parametrizaci k příkazu ad-hoc odhadne datový typ náhradního parametru. Důvody hádání popíšu později v seriálu.

Prozatím se podívejme na několik příkladů s použitím databáze Stack Overflow 2010 na SQL Server 2019 CU 14. Kompatibilita databáze je nastavena na 150 a prahová hodnota nákladů pro paralelismus je nastavena na 50, aby se paralelismu prozatím zabránilo:

ALTER DATABASE SCOPED CONFIGURATION 
    CLEAR PROCEDURE_CACHE;
GO
SELECT U.DisplayName
FROM dbo.Users AS U 
WHERE U.Reputation = 252;
GO
SELECT U.DisplayName
FROM dbo.Users AS U 
WHERE U.Reputation = 25221;
GO
SELECT U.DisplayName
FROM dbo.Users AS U 
WHERE U.Reputation = 252552;

Výsledkem těchto příkazů je šest plánů uložených v mezipaměti, tři Adhoc a tři Připraveno :

Různé uhodnuté typy

Všimněte si různých typů dat parametrů v části Připraveno plány.

Odvozování typu dat

Podrobnosti o tom, jak se každý typ dat odhaduje, jsou složité a neúplně zdokumentované. Jako výchozí bod SQL Server odvodí základní typ z textové reprezentace hodnoty a poté použije nejmenší kompatibilní podtyp.

Pro řetězec čísel bez uvozovek nebo desetinné čárky vybere SQL Server z tinyint , smallint a integer . Pro taková čísla mimo rozsah integer , SQL Server používá numeric s nejmenší možnou přesností. Například číslo 2 147 483 648 je zadáno jako numeric(10,0) . bigint typ se nepoužívá pro parametrizaci na straně serveru. Tento odstavec vysvětluje datové typy vybrané v předchozích příkladech.

Řetězce čísel s desetinná tečka se interpretuje jako numeric , s přesností a měřítkem právě tak velkým, aby obsahoval zadanou hodnotu. Řetězce s předponou se symbolem měny jsou interpretovány jako money . Řetězce ve vědecké notaci se překládají do float . smallmoney a real typy se nepoužívají.

datetime a uniqueidentifer typy nelze odvodit z přirozených formátů řetězců. Chcete-li získat datetime nebo uniqueidentifier typ parametru, musí být doslovná hodnota poskytnuta ve formátu escape ODBC. Například {d '1901-01-01'} , {ts '1900-01-01 12:34:56.790'} nebo {guid 'F85C72AB-15F7-49E9-A949-273C55A6C393'} . Jinak se zamýšlené datum nebo literál UUID zadá jako řetězec. Typy data a času jiné než datetime se nepoužívají.

Obecný řetězec a binární literály se zadávají jako varchar(8000) , nvarchar(4000) nebo varbinary(8000) podle potřeby, pokud literál nepřesahuje 8000 bajtů, v takovém případě max používá se varianta. Toto schéma pomáhá vyhnout se znečištění mezipaměti a nízké úrovni opětovného použití, které by bylo důsledkem použití konkrétních délek.

Není možné použít CAST nebo CONVERT pro nastavení datového typu pro parametry z důvodů, které podrobně uvedu později v této sérii. V další části je příklad tohoto.

Nebudu se zabývat nucenou parametrizací v této sérii, ale chci se zmínit o pravidlech pro odvození datových typů, v takovém případě mají některé důležité rozdíly ve srovnání s jednoduchou parametrizací . Vynucená parametrizace byla přidána až v SQL Server 2005, takže Microsoft měl příležitost začlenit některé lekce z jednoduché parametrizace zkušenosti a nemuseli se příliš starat o problémy se zpětnou kompatibilitou.

Číselné typy

Pro čísla s desetinnou čárkou a celá čísla mimo rozsah integer , pravidla odvozeného typu představují zvláštní problémy pro opětovné použití plánu a znečištění mezipaměti.

Zvažte následující dotaz s použitím desetinných míst:

ALTER DATABASE SCOPED CONFIGURATION 
    CLEAR PROCEDURE_CACHE;
GO
DROP TABLE IF EXISTS dbo.Test;
GO
CREATE TABLE dbo.Test
(
    SomeValue decimal(19,8) NOT NULL
);
GO
SELECT 
    T.SomeValue 
FROM dbo.Test AS T 
WHERE 
    T.SomeValue >= 987.65432 
    AND T.SomeValue < 123456.789;

Tento dotaz splňuje podmínky pro jednoduchou parametrizaci . SQL Server zvolí nejmenší přesnost a měřítko pro parametry, které mohou obsahovat dodané hodnoty. To znamená, že zvolí numeric(8,5) pro 987.65432 a numeric(9,3) pro 123456.789 :

Odvozené číselné datové typy

Tyto odvozené typy neodpovídají decimal(19,8) typ sloupce, takže v plánu provádění se objeví konverze kolem parametru:

Převod na typ sloupce

Tyto konverze představují v tomto konkrétním případě pouze malou neefektivnost běhu. V jiných situacích může neshoda mezi datovým typem sloupce a odvozeným typem parametru zabránit hledání indexu nebo vyžadovat, aby SQL Server vykonal práci navíc při výrobě dynamického hledání.

I když se výsledný prováděcí plán zdá být rozumný, může neshoda typu snadno ovlivnit kvalitu plánu v důsledku vlivu neshody typu na odhad mohutnosti. Vždy je nejlepší používat odpovídající datové typy a věnovat pečlivou pozornost odvozeným typům vyplývajícím z výrazů.

Naplánovat opětovné použití

Hlavním problémem současného plánu jsou konkrétní odvozené typy, které ovlivňují shodu plánů uložených v mezipaměti, a tedy opětovné použití. Spusťte několik dalších dotazů ve stejném obecném tvaru:

SELECT 
    T.SomeValue 
FROM dbo.Test AS T 
WHERE 
    T.SomeValue >= 98.76 
    AND T.SomeValue < 123.4567;
GO
SELECT 
    T.SomeValue 
FROM dbo.Test AS T 
WHERE 
    T.SomeValue >= 1.2 
    AND T.SomeValue < 1234.56789;
GO

Nyní se podívejte na mezipaměť plánu:

SELECT
    CP.usecounts,
    CP.objtype,
    ST.[text]
FROM sys.dm_exec_cached_plans AS CP
CROSS APPLY sys.dm_exec_sql_text (CP.plan_handle) AS ST
WHERE 
    ST.[text] NOT LIKE '%dm_exec_cached_plans%'
    AND ST.[text] LIKE '%SomeValue%Test%'
ORDER BY 
    CP.objtype ASC;

Zobrazuje AdHoc a Připraveno výpis pro každý dotaz, který jsme odeslali:

Samostatná připravená prohlášení

Parametrizovaný text je stejný, ale datové typy parametrů se liší, takže samostatné plány jsou ukládány do mezipaměti a nedochází k opětovnému použití plánu.

Pokud budeme nadále odesílat dotazy s různými kombinacemi měřítka nebo přesnosti, nový Připraveno plán bude vytvořen a uložen do mezipaměti pokaždé. Pamatujte, že odvozený typ každého parametru není omezen datovým typem sloupce, takže bychom mohli skončit s obrovským počtem plánů uložených v mezipaměti v závislosti na odeslaných číselných literálech. Počet kombinací z numeric(1,0) na numeric(38,38) je již velké, než se zamyslíme nad více parametry.

Explicitní parametrizace

Tento problém nenastane, když použijeme explicitní parametrizaci, v ideálním případě zvolíme stejný datový typ jako sloupec, se kterým je parametr porovnáván:

ALTER DATABASE SCOPED CONFIGURATION 
    CLEAR PROCEDURE_CACHE;
GO
DECLARE 
    @stmt nvarchar(4000) =
        N'SELECT T.SomeValue FROM dbo.Test AS T WHERE T.SomeValue >= @P1 AND T.SomeValue < @P2;',
    @params nvarchar(4000) =
        N'@P1 numeric(19,8), @P2 numeric(19,8)';
 
EXECUTE sys.sp_executesql 
    @stmt, 
    @params, 
    @P1 = 987.65432, 
    @P2 = 123456.789;
 
EXECUTE sys.sp_executesql 
    @stmt, 
    @params, 
    @P1 = 98.76, 
    @P2 = 123.4567;
 
EXECUTE sys.sp_executesql 
    @stmt, 
    @params, 
    @P1 = 1.2, 
    @P2 = 1234.56789;

S explicitní parametrizací zobrazí dotaz mezipaměti plánu pouze jeden plán uložený v mezipaměti, použitý třikrát a není potřeba žádný typ konverze:

Explicitní parametrizace

Jako poslední poznámku jsem použil decimal a numeric zaměnitelně v této sekci. Jsou technicky různé typy, i když jsou doloženy jako synonyma a chovají se ekvivalentně. Obvykle tomu tak je, ale ne vždy:

-- Raises error 8120:
-- Column 'dbo.Test.SomeValue' is invalid in the select list
-- because it is not contained in either an aggregate function
-- or the GROUP BY clause.
SELECT CONVERT(decimal(19,8), T.SomeValue)
FROM dbo.Test AS T 
GROUP BY CONVERT(numeric(19,8), T.SomeValue);

Je to pravděpodobně malá chyba analyzátoru, ale přesto se vyplatí být konzistentní (pokud nepíšete článek a nechcete upozornit na zajímavou výjimku).

Aritmetické operátory

Je tu ještě jeden okrajový případ, kterému se chci věnovat, na základě příkladu uvedeného v dokumentaci, ale trochu podrobněji (a možná přesněji):

-- The dbo.LinkTypes table contains two rows
 
-- Uses simple parameterization
SELECT r = CONVERT(float, 1./ 7) 
FROM dbo.LinkTypes AS LT;
 
-- No simple parameterization due to
-- constant-constant comparison
SELECT r = CONVERT(float, 1./ 7) 
FROM dbo.LinkTypes AS LT 
WHERE 1 = 1;

Výsledky jsou různé, jak je zdokumentováno:

Různé výsledky

S jednoduchou parametrizací

Když jednoduchá parametrizace dojde, SQL Server parametrizuje obě doslovné hodnoty. 1. hodnota je zapsána jako numeric(1,0) podle očekávání. Poněkud nekonzistentně, 7 je zapsán jako integer (nikoli tinyint ). Pravidla odvození typu byla vytvořena v průběhu času různými týmy. Chování je zachováno, aby nedošlo k porušení staršího kódu.

Další krok zahrnuje / aritmetický operátor. SQL Server vyžaduje před provedením rozdělení kompatibilní typy. Dané numeric (decimal ) má vyšší prioritu datového typu než integer , integer budou převedeny na numeric .

SQL Server potřebuje implicitně převést integer na numeric . Ale jakou přesnost a měřítko použít? Odpověď by mohla být založena na původním literálu, jako to dělá SQL Server za jiných okolností, ale vždy používá numeric(10) zde.

Datový typ výsledku dělení numeric(1,0) pomocí numeric(10,0) je určen jiným soubor pravidel uvedených v dokumentaci pro přesnost, měřítko a délku. Zapojením čísel do tam uvedených vzorců pro přesnost výsledku a měřítko máme:

  • Přesnost výsledku:
    • p1 – s1 + s2 + max(6, s1 + p2 + 1)
    • =1 – 0 + 0 + max(6, 0 + 10 + 1)
    • =1 + max(6, 11)
    • =1 + 11
    • =12
  • Měřítko výsledku:
    • max(6, s1 + p2 + 1)
    • =max(6, 0 + 10 + 1)
    • =max(6, 11)
    • =11

Datový typ 1. / 7 je tedy numeric(12, 11) . Tato hodnota je poté převedena na float jak bylo požadováno a zobrazeno jako 0.14285714285 (s 11 číslicemi za desetinnou čárkou).

Bez jednoduché parametrizace

Pokud není provedena jednoduchá parametrizace, zobrazí se 1. literál je zadán jako numeric(1,0) jako dříve. 7 je zpočátku napsáno jako integer také jak bylo vidět dříve. Klíčovým rozdílem je integer se převede na numeric(1,0) , takže operátor divize má běžné typy, se kterými může pracovat. Toto je nejmenší přesnost a měřítko, které může obsahovat hodnotu 7 . Pamatujte na jednoduchou použitou parametrizaci numeric(10,0) zde.

Vzorce přesnosti a měřítka pro dělení numeric(1,0) podle numeric(1,0) zadejte datový typ výsledku numeric(7,6) :

  • Přesnost výsledku:
    • p1 – s1 + s2 + max(6, s1 + p2 + 1)
    • =1 – 0 + 0 + max(6, 0 + 1 + 1)
    • =1 + max(6, 2)
    • =1 + 6
    • =7
  • Měřítko výsledku:
    • max(6, s1 + p2 + 1)
    • =max(6, 0 + 1 + 1)
    • =max(6, 2)
    • =6

Po konečné konverzi na float , zobrazený výsledek je 0.142857 (se šesti číslicemi za desetinnou čárkou).

Pozorovaný rozdíl ve výsledcích je tedy způsoben přechodným odvozením typu (numeric(12,11) vs. numeric(7,6) ) spíše než konečný převod na float .

Pokud potřebujete další důkaz převodu na float nenese odpovědnost, zvažte:

-- Simple parameterization
SELECT r = CONVERT(decimal(13,12), 1. / 7)
FROM dbo.LinkTypes AS LT;
 
-- No simple parameterization
SELECT r = CONVERT(decimal(13,12), 1. / 7)
FROM dbo.LinkTypes AS LT 
OPTION (MAXDOP 1);

Výsledek s desetinným místem

Výsledky se liší hodnotou a rozsahem jako dříve.

Tato část nepokrývá všechny zvláštnosti odvození a převodu datových typů pomocí jednoduché parametrizace jakýmkoli způsobem. Jak již bylo řečeno, pokud je to možné, je lepší používat explicitní parametry se známými datovými typy.

Konec 2. části

Další část této série popisuje, jak jednoduchá parametrizace ovlivňuje prováděcí plány.


  1. ORACLE SQL:Doplňte chybějící data

  2. 5 způsobů, jak spustit SQL skript ze souboru v SQLite

  3. Příkaz CASE v klauzuli WHERE v SQL Server 2008

  4. Jak mohu uzamknout tabulku při čtení pomocí Entity Framework?