sql >> Databasteknik >  >> RDS >> Database

Enkel parametrering och triviala planer — Del 2

Parameterdatatyper

Som nämnts i den första delen av denna serie, är en av anledningarna till att det är bättre att explicit parametrisera så att du har full kontroll över parameterdatatyper. Enkel parametrisering har ett antal egenheter inom detta område, vilket kan resultera i att fler parametriserade planer cachelagras än förväntat, eller att man hittar andra resultat jämfört med den oparameteriserade versionen.

När SQL Server tillämpar enkel parameterisering till ett ad-hoc-uttalande gör det en gissning om datatypen för ersättningsparametern. Jag kommer att täcka skälen till gissningarna senare i serien.

Låt oss för närvarande titta på några exempel med Stack Overflow 2010-databasen på SQL Server 2019 CU 14. Databaskompatibilitet är inställd på 150, och kostnadströskeln för parallellitet är satt till 50 för att undvika parallellism för närvarande:

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;

Dessa uttalanden resulterar i sex cachade planer, tre Adhoc och tre Förberedda :

Olika gissningstyper

Lägg märke till de olika parameterdatatyperna i Förberedd planer.

Datatypinferens

Detaljerna för hur varje datatyp gissas är komplexa och ofullständigt dokumenterade. Som utgångspunkt härleder SQL Server en grundläggande typ från den textmässiga representationen av värdet och använder sedan den minsta kompatibla undertypen.

För en sträng med siffror utan citattecken eller decimalkomma väljer SQL Server från tinyint , smallint och integer . För sådana tal utanför intervallet för ett integer , SQL Server använder numeric med minsta möjliga precision. Till exempel skrivs numret 2 147 483 648 som numeric(10,0) . Den bigint typ används inte för parametrering på serversidan. Detta stycke förklarar de datatyper som valts i de tidigare exemplen.

Strängar av nummer med en decimalkomma tolkas som numeric , med en precision och skala precis stor nog att innehålla det angivna värdet. Strängar prefixerade med en valutasymbol tolkas som money . Strängar i vetenskaplig notation översätts till float . smallmoney och real typer används inte.

datetime och uniqueidentifer typer kan inte härledas från naturliga strängformat. För att få en datetime eller uniqueidentifier parametertyp måste det bokstavliga värdet anges i ODBC-escape-format. Till exempel {d '1901-01-01'} , {ts '1900-01-01 12:34:56.790'} , eller {guid 'F85C72AB-15F7-49E9-A949-273C55A6C393'} . Annars skrivs det avsedda datumet eller UUID-literalen som en sträng. Andra datum- och tidstyper än datetime används inte.

Allmän sträng och binära bokstaver skrivs som varchar(8000) , nvarchar(4000) , eller varbinary(8000) såvida det är lämpligt, om inte bokstaven överstiger 8000 byte i vilket fall max variant används. Det här schemat hjälper till att undvika cacheförorening och låg återanvändning som skulle bli resultatet av att använda specifika längder.

Det är inte möjligt att använda CAST eller CONVERT för att ställa in datatypen för parametrar av skäl som jag kommer att beskriva senare i den här serien. Det finns ett exempel på detta i nästa avsnitt.

Jag kommer inte att täcka tvingad parameterisering i den här serien, men jag vill nämna att reglerna för datatypinferens i så fall har några viktiga skillnader jämfört med enkel parameterisering . Tvångsparametrisering lades inte till förrän SQL Server 2005, så Microsoft hade möjlighet att införliva några lärdomar från den enkla parameteriseringen erfarenhet och behövde inte oroa sig mycket för problem med bakåtkompatibilitet.

Numeriska typer

För tal med en decimalkomma och heltal utanför intervallet integer , de härledda typreglerna ger speciella problem för planåteranvändning och cacheförorening.

Tänk på följande fråga med decimaler:

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;

Denna fråga kvalificerar sig för enkel parameterisering . SQL Server väljer den minsta precision och skala för parametrarna som kan innehålla de angivna värdena. Det betyder att den väljer numeric(8,5) för 987.65432 och numeric(9,3) för 123456.789 :

Utledda numeriska datatyper

Dessa härledda typer matchar inte decimal(19,8) typ av kolumn, så en konvertering runt parametern visas i exekveringsplanen:

Konvertering till kolumntyp

Dessa omvandlingar representerar bara en liten ineffektivitet vid körning i det här specifika fallet. I andra situationer kan en oöverensstämmelse mellan kolumndatatypen och den härledda typen av en parameter förhindra en indexsökning eller kräva att SQL Server gör extra arbete för att skapa en dynamisk sökning.

Även där den resulterande exekveringsplanen verkar rimlig kan en typfelmatchning lätt påverka plankvaliteten på grund av effekten av typomatchningen på kardinalitetsuppskattningen. Det är alltid bäst att använda matchande datatyper och att vara noggrann uppmärksam på de härledda typerna som härrör från uttryck.

Planera återanvändning

Huvudproblemet med den nuvarande planen är de specifika härledda typerna som påverkar cachad planmatchning och därför återanvändning. Låt oss köra ytterligare ett par frågor i samma allmänna form:

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

Titta nu på planens cache:

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;

Den visar en AdHoc och Förberedd uttalande för varje fråga vi skickade:

Separata förberedda uttalanden

Den parametriserade texten är densamma, men parameterdatatyperna är olika, så separata planer cachelagras och ingen återanvändning av planen sker.

Om vi ​​fortsätter att skicka in frågor med olika kombinationer av skala eller precision, kommer en ny Förberedd plan kommer att skapas och cachelagras varje gång. Kom ihåg att den härledda typen av varje parameter inte är begränsad av kolumndatatypen, så vi kan sluta med ett enormt antal cachade planer, beroende på de numeriska bokstavliga som skickas in. Antalet kombinationer från numeric(1,0) till numeric(38,38) är redan stor innan vi tänker på flera parametrar.

Explicit parametrering

Det här problemet uppstår inte när vi använder explicit parametrering, och vi väljer helst samma datatyp som kolumnen parametern jämförs med:

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;

Med explicit parameterisering visar plancachefrågan endast en plan cachad, använd tre gånger och inga typkonverteringar behövs:

Explicit parametrering

Som en sista anmärkning har jag använt decimal och numeric omväxlande i detta avsnitt. De är tekniskt olika typer, även om det är dokumenterat att de är synonymer och beter sig likvärdigt. Detta är vanligtvis fallet, men inte alltid:

-- 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);

Det är förmodligen ett litet parserfel, men det lönar sig ändå att vara konsekvent (såvida du inte skriver en artikel och vill peka ut ett intressant undantag).

Aritmetiska operatorer

Det finns ett annat kantfall jag vill ta upp, baserat på ett exempel som ges i dokumentationen, men lite mer detaljerat (och kanske exakthet):

-- 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;

Resultaten är olika, som dokumenterats:

Olika resultat

Med enkel parametrering

När enkel parametrering inträffar, parametrerar SQL Server båda bokstavsvärdena. 1. värdet skrivs som numeric(1,0) som förväntat. Något inkonsekvent, 7 skrivs som integer (inte tinyint ). Reglerna för typinferens har byggts upp över tid, av olika team. Beteenden upprätthålls för att undvika att bryta äldre kod.

Nästa steg involverar / aritmetisk operator. SQL Server kräver kompatibla typer innan uppdelningen utförs. Givet numeric (decimal ) har en högre datatypsprioritet än integer , integer kommer att konverteras till numeric .

SQL Server måste implicit konvertera integer till numeric . Men vilken precision och skala ska man använda? Svaret kan baseras på den ursprungliga bokstavliga, som SQL Server gör under andra omständigheter, men den använder alltid numeric(10) här.

Datatypen för resultatet av att dividera en numeric(1,0) med en numeric(10,0) bestäms av annan uppsättning regler, givna i dokumentationen för precision, skala och längd. Om vi ​​kopplar in siffrorna i formlerna för resultatprecision och skala som ges där, har vi:

  • Resultatprecision:
    • p1 – s1 + s2 + max(6, s1 + p2 + 1)
    • =1 – 0 + 0 + max(6, 0 + 10 + 1)
    • =1 + max(6, 11)
    • =1 + 11
    • =12
  • Resultatskala:
    • max(6, s1 + p2 + 1)
    • =max(6, 0 + 10 + 1)
    • =max(6, 11)
    • =11

Datatypen för 1. / 7 är därför numeric(12, 11) . Detta värde konverteras sedan till float som begärts och visas som 0.14285714285 (med 11 siffror efter decimaltecknet).

Utan enkel parameterisering

När enkel parametrering inte utförs visas 1. literal skrivs som numeric(1,0) som förut. 7 skrivs initialt som integer också som sett tidigare. Den viktigaste skillnaden är integer konverteras till numeric(1,0) , så divisionsoperatören har vanliga typer att arbeta med. Detta är den minsta precision och skala som kan innehålla värdet 7 . Kom ihåg enkel parametrering som används numeric(10,0) här.

Precisions- och skalformlerna för att dividera numeric(1,0) av numeric(1,0) ge resultatdatatypen numeric(7,6) :

  • Resultatprecision:
    • p1 – s1 + s2 + max(6, s1 + p2 + 1)
    • =1 – 0 + 0 + max(6, 0 + 1 + 1)
    • =1 + max(6, 2)
    • =1 + 6
    • =7
  • Resultatskala:
    • max(6, s1 + p2 + 1)
    • =max(6, 0 + 1 + 1)
    • =max(6, 2)
    • =6

Efter den sista konverteringen till float , det visade resultatet är 0.142857 (med sex siffror efter decimaltecknet).

Den observerade skillnaden i resultaten beror därför på interimstyp härledning (numeric(12,11) kontra numeric(7,6) ) istället för den slutliga omvandlingen till float .

Om du behöver ytterligare bevis omvandlingen till float inte är ansvarig, överväg:

-- 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);

Resultat med decimal

Resultaten skiljer sig i värde och skala som tidigare.

Det här avsnittet täcker inte varje egendom av datatyps slutledning och konvertering med enkel parameterisering på något sätt. Som sagt tidigare är det bättre att använda explicita parametrar med kända datatyper där det är möjligt.

Slutet av del 2

Nästa del av den här serien beskriver hur enkel parametrisering påverkar genomförandeplaner.


  1. Installera WordPress med WP-CLI

  2. Min Välj SUM-fråga returnerar null. Det bör returnera 0

  3. Hur får man ett float-resultat genom att dividera två heltalsvärden med T-SQL?

  4. TNSPING OK men sqlplus ger ORA-12154?