sql >> Database >  >> RDS >> Database

Eenvoudige parametrering en triviale plannen - deel 2

Parametergegevenstypen

Zoals vermeld in het eerste deel van deze serie, is een van de redenen waarom het beter is om expliciet te parametriseren, dat je volledige controle hebt over parameterdatatypes. Eenvoudige parametrering heeft een aantal eigenaardigheden op dit gebied, wat ertoe kan leiden dat meer geparametreerde plannen in de cache worden opgeslagen dan verwacht, of dat andere resultaten worden gevonden in vergelijking met de niet-geparametreerde versie.

Wanneer SQL Server eenvoudige parametrering toepast naar een ad-hoc-instructie, maakt het een schatting over het gegevenstype van de vervangende parameter. Ik zal de redenen voor het raden later in de serie bespreken.

Laten we voorlopig eens kijken naar enkele voorbeelden van het gebruik van de Stack Overflow 2010-database op SQL Server 2019 CU 14. Databasecompatibiliteit is ingesteld op 150 en de kostendrempel voor parallellisme is ingesteld op 50 om parallellisme voorlopig te voorkomen:

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;

Deze verklaringen resulteren in zes plannen in de cache, drie Adhoc en drie Voorbereid :

Verschillende geraden typen

Let op de verschillende typen parametergegevens in de Prepared plannen.

Gegevenstype-inferentie

De details van hoe elk gegevenstype wordt geraden, zijn complex en onvolledig gedocumenteerd. Als uitgangspunt leidt SQL Server een basistype af uit de tekstuele weergave van de waarde en gebruikt vervolgens het kleinste compatibele subtype.

Voor een reeks getallen zonder aanhalingstekens of een decimaalteken kiest SQL Server uit tinyint , smallint , en integer . Voor zulke getallen buiten het bereik van een integer , SQL Server gebruikt numeric met de kleinst mogelijke precisie. Het getal 2.147.483.648 wordt bijvoorbeeld getypt als numeric(10,0) . De bigint type wordt niet gebruikt voor parameterisatie aan de serverzijde. In deze paragraaf worden de gegevenstypen uitgelegd die in de voorgaande voorbeelden zijn geselecteerd.

Cijferreeksen met een decimaalteken wordt geïnterpreteerd als numeric , met een precisie en schaal die net groot genoeg is om de opgegeven waarde te bevatten. Tekenreeksen met een valutasymbool worden geïnterpreteerd als money . Strings in wetenschappelijke notatie vertalen naar float . De smallmoney en real typen zijn niet in dienst.

De datetime en uniqueidentifer typen kunnen niet worden afgeleid uit natuurlijke tekenreeksen. Om een ​​datetime te krijgen of uniqueidentifier parametertype, moet de letterlijke waarde worden opgegeven in de ODBC-escape-indeling. Bijvoorbeeld {d '1901-01-01'} , {ts '1900-01-01 12:34:56.790'} , of {guid 'F85C72AB-15F7-49E9-A949-273C55A6C393'} . Anders wordt de beoogde datum of UUID-letterlijk getypt als een tekenreeks. Andere datum- en tijdtypen dan datetime worden niet gebruikt.

Algemene tekenreeksen en binaire letterlijke waarden worden getypt als varchar(8000) , nvarchar(4000) , of varbinary(8000) indien van toepassing, tenzij de letterlijke waarde groter is dan 8000 bytes, in welk geval de max variant wordt gebruikt. Dit schema helpt de cache-vervuiling en het lage hergebruikniveau te voorkomen die het gevolg zouden zijn van het gebruik van specifieke lengtes.

Het is niet mogelijk om CAST . te gebruiken of CONVERT om het gegevenstype voor parameters in te stellen om redenen die ik later in deze serie zal toelichten. Een voorbeeld hiervan vindt u in het volgende gedeelte.

Ik ga niet in op geforceerde parametrering in deze serie, maar ik wil wel vermelden dat de regels voor datatype-inferentie in dat geval enkele belangrijke verschillen hebben in vergelijking met eenvoudige parametrering . Geforceerde parametrering werd pas toegevoegd in SQL Server 2005, dus Microsoft had de mogelijkheid om enkele lessen uit de eenvoudige parametrering op te nemen ervaring, en hoefden zich niet veel zorgen te maken over achterwaartse compatibiliteitsproblemen.

Numerieke typen

Voor getallen met een decimale punt en gehele getallen buiten het bereik van integer , leveren de afgeleide typeregels speciale problemen op voor hergebruik van plannen en cache-vervuiling.

Beschouw de volgende zoekopdracht met decimalen:

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;

Deze zoekopdracht komt in aanmerking voor eenvoudige parametrering . SQL Server kiest de kleinste precisie en schaal voor de parameters die de opgegeven waarden kunnen bevatten. Dit betekent dat het numeric(8,5) . kiest voor 987.65432 en numeric(9,3) voor 123456.789 :

Afgeleide numerieke gegevenstypen

Deze afgeleide typen komen niet overeen met de decimal(19,8) type van de kolom, zodat er een conversie rond de parameter verschijnt in het uitvoeringsplan:

Conversie naar kolomtype

Deze conversies vertegenwoordigen in dit specifieke geval slechts een kleine runtime-inefficiëntie. In andere situaties kan een mismatch tussen het kolomgegevenstype en het afgeleide type van een parameter een indexzoekactie verhinderen of vereisen dat SQL Server extra werk doet om een ​​dynamische zoekactie te maken.

Zelfs als het resulterende uitvoeringsplan redelijk lijkt, kan een typemismatch gemakkelijk de kwaliteit van het plan beïnvloeden vanwege het effect van de typemismatch op de kardinaliteitsschatting. Het is altijd het beste om overeenkomende gegevenstypen te gebruiken en zorgvuldige aandacht te besteden aan de afgeleide typen die resulteren uit expressies.

Hergebruik plannen

Het belangrijkste probleem met het huidige plan zijn de specifieke afgeleide typen die van invloed zijn op het matchen van gecachte plannen en dus hergebruik. Laten we nog een paar zoekopdrachten van dezelfde algemene vorm uitvoeren:

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

Kijk nu naar de plancache:

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;

Het toont een AdHoc en Voorbereid verklaring voor elke vraag die we hebben ingediend:

Afzonderlijke voorbereide verklaringen

De geparametriseerde tekst is hetzelfde, maar de parametergegevenstypen zijn anders, dus afzonderlijke plannen worden in de cache opgeslagen en er vindt geen hergebruik van plannen plaats.

Als we doorgaan met het indienen van zoekopdrachten met verschillende combinaties van schaal of precisie, wordt een nieuwe Voorbereid plan wordt elke keer gemaakt en in de cache opgeslagen. Onthoud dat het afgeleide type van elke parameter niet wordt beperkt door het gegevenstype van de kolom, dus we kunnen eindigen met een enorm aantal plannen in de cache, afhankelijk van de ingediende numerieke literals. Het aantal combinaties van numeric(1,0) naar numeric(38,38) is al groot voordat we over meerdere parameters nadenken.

Expliciete parametrering

Dit probleem doet zich niet voor wanneer we expliciete parametrering gebruiken, idealiter hetzelfde gegevenstype kiezen als de kolom waarmee de parameter wordt vergeleken:

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;

Met expliciete parametrering toont de plancachequery slechts één plan in de cache, drie keer gebruikt en geen typeconversies nodig:

Expliciete parametrering

Als laatste kanttekening heb ik decimal gebruikt en numeric door elkaar in dit gedeelte. Ze zijn technisch verschillende typen, hoewel gedocumenteerd synoniemen zijn en zich gelijkwaardig gedragen. Dit is meestal het geval, maar niet altijd:

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

Het is waarschijnlijk een kleine parserbug, maar het loont de moeite om consistent te zijn (tenzij je een artikel schrijft en je op een interessante uitzondering wilt wijzen).

Rekenkundige operators

Er is nog een ander randgeval dat ik wil behandelen, gebaseerd op een voorbeeld in de documentatie, maar in wat meer detail (en misschien nauwkeurigheid):

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

De resultaten zijn anders, zoals gedocumenteerd:

Verschillende resultaten

Met eenvoudige parametrering

Wanneer eenvoudige parametrering optreedt, parametriseert SQL Server beide letterlijke waarden. De 1. waarde wordt getypt als numeric(1,0) zoals verwacht. Enigszins inconsistent, de 7 wordt getypt als integer (niet tinyint ). De regels voor type-inferentie zijn in de loop van de tijd ontwikkeld door verschillende teams. Gedrag wordt gehandhaafd om te voorkomen dat de oude code wordt gebroken.

De volgende stap omvat de / rekenkundige operator. SQL Server vereist compatibele typen voordat de deling kan worden uitgevoerd. Gegeven numeric (decimal ) heeft een hogere prioriteit voor het gegevenstype dan integer , het integer wordt geconverteerd naar numeric .

SQL Server moet de integer impliciet converteren naar numeric . Maar welke precisie en schaal gebruiken? Het antwoord kan gebaseerd zijn op de originele letterlijke tekst, zoals SQL Server in andere omstandigheden doet, maar het gebruikt altijd numeric(10) hier.

Het gegevenstype van het resultaat van het delen van een numeric(1,0) door een numeric(10,0) wordt bepaald door een ander set regels, gegeven in de documentatie voor precisie, schaal en lengte. Door de getallen in de formules voor resultaatprecisie en schaal in te voeren die daar worden gegeven, hebben we:

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

Het gegevenstype 1. / 7 is daarom numeric(12, 11) . Deze waarde wordt vervolgens geconverteerd naar float zoals gevraagd en weergegeven als 0.14285714285 (met 11 cijfers achter de komma).

Zonder eenvoudige parametrering

Wanneer eenvoudige parametrering niet wordt uitgevoerd, wordt de 1. letterlijk wordt getypt als numeric(1,0) zoals eerder. De 7 wordt aanvankelijk getypt als integer ook zoals eerder gezien. Het belangrijkste verschil is het integer wordt geconverteerd naar numeric(1,0) , dus de divisie-operator heeft veelvoorkomende typen om mee te werken. Dit is de kleinste precisie en schaal die de waarde 7 . kan bevatten . Onthoud de eenvoudige parametrering die wordt gebruikt numeric(10,0) hier.

De precisie- en schaalformules voor het delen van numeric(1,0) door numeric(1,0) geef een resultaatgegevenstype van numeric(7,6) :

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

Na de laatste conversie naar float , het weergegeven resultaat is 0.142857 (met zes cijfers achter de komma).

Het waargenomen verschil in de resultaten is daarom te wijten aan tussentijdse typeafleiding (numeric(12,11) vs. numeric(7,6) ) in plaats van de uiteindelijke conversie naar float .

Als u meer bewijs nodig heeft, is de conversie naar float niet verantwoordelijk is, overweeg dan:

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

Resultaat met decimaal

De resultaten verschillen in waarde en schaal als voorheen.

Deze sectie behandelt niet elke eigenaardigheid van gegevenstype-inferentie en -conversie met eenvoudige parametrering hoe dan ook. Zoals eerder gezegd, kunt u waar mogelijk beter expliciete parameters gebruiken met bekende gegevenstypen.

Einde van deel 2

Het volgende deel van deze serie beschrijft hoe eenvoudige parametrering beïnvloedt uitvoeringsplannen.


  1. Hoe kan ik zien welke transactie een vergrendelingsstatus Wachten op tabelmetagegevens veroorzaakt?

  2. Correct gebruik van transacties in SQL Server

  3. Hoe vind ik de MySQL my.cnf-locatie

  4. Een database ontwerpen voor een online banenportaal