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.