Database
 sql >> Datenbank >  >> RDS >> Database

Einfache Parametrisierung und triviale Pläne – Teil 2

Parameterdatentypen

Wie im ersten Teil dieser Serie erwähnt, ist einer der Gründe für eine explizite Parametrisierung besser, damit Sie die volle Kontrolle über die Parameterdatentypen haben. Die einfache Parametrisierung weist in diesem Bereich eine Reihe von Macken auf, die dazu führen können, dass mehr parametrisierte Pläne als erwartet zwischengespeichert werden oder andere Ergebnisse im Vergleich zur nicht parametrisierten Version gefunden werden.

Wenn SQL Server einfache Parametrisierung anwendet zu einer Ad-hoc-Anweisung macht es eine Vermutung über den Datentyp des Ersetzungsparameters. Ich werde die Gründe für das Raten später in der Serie behandeln.

Schauen wir uns vorerst einige Beispiele an, die die Stack Overflow 2010-Datenbank auf SQL Server 2019 CU 14 verwenden. Die Datenbankkompatibilität ist auf 150 festgelegt, und die Kostenschwelle für Parallelität ist auf 50 festgelegt, um Parallelität vorerst zu vermeiden:

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;

Diese Anweisungen führen zu sechs zwischengespeicherten Plänen, drei Adhoc und drei Vorbereitet :

Verschiedene erratene Typen

Beachten Sie die unterschiedlichen Parameterdatentypen in Prepared Pläne.

Datentyp-Inferenz

Die Details, wie jeder Datentyp erraten wird, sind komplex und unvollständig dokumentiert. Als Ausgangspunkt leitet SQL Server einen Basistyp aus der Textdarstellung des Werts ab und verwendet dann den kleinsten kompatiblen Untertyp.

Für eine Zahlenfolge ohne Anführungszeichen oder Dezimalpunkt wählt SQL Server aus tinyint , smallint , und integer . Für solche Zahlen außerhalb des Bereichs einer integer verwendet SQL Server numeric mit der kleinstmöglichen Präzision. Beispielsweise wird die Zahl 2.147.483.648 als numeric(10,0) eingegeben . Die bigint type wird nicht für die serverseitige Parametrisierung verwendet. Dieser Abschnitt erläutert die in den vorherigen Beispielen ausgewählten Datentypen.

Zahlenfolgen mit ein Dezimalpunkt werden als numeric interpretiert , mit einer Genauigkeit und Skalierung, die gerade groß genug ist, um den bereitgestellten Wert aufzunehmen. Zeichenfolgen mit vorangestelltem Währungssymbol werden als money interpretiert . Strings in wissenschaftlicher Notation werden in float übersetzt . Das smallmoney und real Typen werden nicht verwendet.

Die datetime und uniqueidentifer Typen können nicht aus natürlichen Zeichenkettenformaten abgeleitet werden. Um eine datetime zu erhalten oder uniqueidentifier -Parametertyp muss der Literalwert im ODBC-Escape-Format bereitgestellt werden. Zum Beispiel {d '1901-01-01'} , {ts '1900-01-01 12:34:56.790'} , oder {guid 'F85C72AB-15F7-49E9-A949-273C55A6C393'} . Andernfalls wird das beabsichtigte Datum oder UUID-Literal als Zeichenfolge eingegeben. Andere Datums- und Zeittypen als datetime werden nicht verwendet.

Allgemeine String- und Binärliterale werden als varchar(8000) eingegeben , nvarchar(4000) , oder varbinary(8000) wie angemessen, es sei denn, das Literal überschreitet 8000 Bytes, in diesem Fall max Variante verwendet wird. Dieses Schema trägt dazu bei, die Cache-Verschmutzung und die geringe Wiederverwendung zu vermeiden, die sich aus der Verwendung bestimmter Längen ergeben würden.

Es ist nicht möglich, CAST zu verwenden oder CONVERT um den Datentyp für Parameter festzulegen, aus Gründen, die ich später in dieser Serie näher erläutern werde. Ein Beispiel dafür finden Sie im nächsten Abschnitt.

Ich werde erzwungene Parametrisierung nicht behandeln in dieser Serie, aber ich möchte erwähnen, dass die Regeln für die Datentypinferenz in diesem Fall einige wichtige Unterschiede im Vergleich zur einfachen Parametrisierung aufweisen . Die erzwungene Parametrisierung wurde erst mit SQL Server 2005 hinzugefügt, sodass Microsoft die Möglichkeit hatte, einige Lehren aus der einfachen Parametrisierung zu integrieren Erfahrung und musste sich keine großen Gedanken über Probleme mit der Abwärtskompatibilität machen.

Numerische Typen

Für Zahlen mit Dezimalpunkt und ganze Zahlen außerhalb des Bereichs integer , stellen die abgeleiteten Typregeln besondere Probleme für die Wiederverwendung von Plänen und Cache-Verschmutzung dar.

Betrachten Sie die folgende Abfrage mit Dezimalzahlen:

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;

Diese Abfrage eignet sich für einfache Parametrisierung . SQL Server wählt die kleinste Genauigkeit und Skalierung für die Parameter aus, die die bereitgestellten Werte enthalten können. Das heißt, es wählt numeric(8,5) für 987.65432 und numeric(9,3) für 123456.789 :

Abgeleitete numerische Datentypen

Diese abgeleiteten Typen stimmen nicht mit decimal(19,8) überein Typ der Spalte, sodass im Ausführungsplan eine Konvertierung um den Parameter herum erscheint:

Konvertierung in Spaltentyp

Diese Konvertierungen stellen in diesem speziellen Fall nur eine geringe Laufzeitineffizienz dar. In anderen Situationen kann eine Nichtübereinstimmung zwischen dem Spaltendatentyp und dem abgeleiteten Typ eines Parameters eine Indexsuche verhindern oder erfordern, dass SQL Server zusätzliche Arbeit leistet, um eine dynamische Suche zu erstellen.

Selbst wenn der resultierende Ausführungsplan vernünftig erscheint, kann eine Typenabweichung die Planqualität aufgrund der Auswirkung der Typenabweichung auf die Kardinalitätsschätzung leicht beeinträchtigen. Es ist immer am besten, übereinstimmende Datentypen zu verwenden und sorgfältig auf die abgeleiteten Typen zu achten, die sich aus Ausdrücken ergeben.

Wiederverwendung planen

Das Hauptproblem beim aktuellen Plan sind die spezifischen abgeleiteten Typen, die sich auf den zwischengespeicherten Planabgleich und damit auf die Wiederverwendung auswirken. Lassen Sie uns ein paar weitere Abfragen derselben allgemeinen Form ausführen:

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

Sehen Sie sich nun den Plan-Cache an:

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;

Es zeigt ein AdHoc und Vorbereitet Erklärung für jede von uns übermittelte Anfrage:

Separate vorbereitete Anweisungen

Der parametrisierte Text ist derselbe, aber die Parameterdatentypen sind unterschiedlich, sodass separate Pläne zwischengespeichert werden und keine Wiederverwendung von Plänen erfolgt.

Wenn wir weiterhin Abfragen mit unterschiedlichen Kombinationen von Skalierung oder Genauigkeit übermitteln, wird ein neues Vorbereitet Plan wird jedes Mal erstellt und zwischengespeichert. Denken Sie daran, dass der abgeleitete Typ jedes Parameters nicht durch den Spaltendatentyp begrenzt ist, sodass wir abhängig von den übermittelten numerischen Literalen eine enorme Anzahl von zwischengespeicherten Plänen erhalten könnten. Die Anzahl der Kombinationen aus numeric(1,0) zu numeric(38,38) ist bereits groß, bevor wir an mehrere Parameter denken.

Explizite Parametrisierung

Dieses Problem tritt nicht auf, wenn wir eine explizite Parametrisierung verwenden und idealerweise denselben Datentyp wie die Spalte wählen, mit der der Parameter verglichen wird:

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;

Bei expliziter Parametrisierung zeigt die Plan-Cache-Abfrage nur einen Plan, der dreimal zwischengespeichert und dreimal verwendet wird, und es sind keine Typkonvertierungen erforderlich:

Explizite Parametrisierung

Als letzte Randbemerkung habe ich decimal verwendet und numeric austauschbar in diesem Abschnitt. Sie sind technisch gesehen unterschiedliche Typen, obwohl dokumentiert, dass sie Synonyme sind und sich äquivalent verhalten. Dies ist normalerweise der Fall, aber nicht immer:

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

Es handelt sich wahrscheinlich um einen kleinen Parser-Bug, aber es lohnt sich trotzdem, konsequent zu sein (es sei denn, Sie schreiben einen Artikel und möchten auf eine interessante Ausnahme hinweisen).

Arithmetische Operatoren

Es gibt noch einen weiteren Grenzfall, den ich ansprechen möchte, basierend auf einem Beispiel in der Dokumentation, aber etwas detaillierter (und vielleicht genauer):

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

Die Ergebnisse sind unterschiedlich, wie dokumentiert:

Unterschiedliche Ergebnisse

Mit einfacher Parametrierung

Bei einfacher Parametrierung auftritt, parametrisiert SQL Server beide Literalwerte. Der 1. value wird als numeric(1,0) eingegeben wie erwartet. Etwas widersprüchlich die 7 wird als integer eingegeben (nicht tinyint ). Die Regeln der Typinferenz wurden im Laufe der Zeit von verschiedenen Teams erstellt. Verhaltensweisen werden beibehalten, um zu verhindern, dass Legacy-Code beschädigt wird.

Der nächste Schritt beinhaltet den / arithmetischer Operator. SQL Server erfordert kompatible Typen, bevor die Division durchgeführt wird. Gegeben numeric (decimal ) hat eine höhere Datentyppriorität als integer , die integer wird in numeric umgewandelt .

SQL Server muss den integer implizit konvertieren zu numeric . Aber welche Genauigkeit und Skalierung soll verwendet werden? Die Antwort könnte auf dem ursprünglichen Literal basieren, wie es SQL Server unter anderen Umständen tut, aber es verwendet immer numeric(10) hier.

Der Datentyp des Ergebnisses der Division von numeric(1,0) durch einen numeric(10,0) wird anders bestimmt Satz von Regeln, die in der Dokumentation für Genauigkeit, Maßstab und Länge angegeben sind. Setzt man die Zahlen in die dort angegebenen Formeln für Ergebnispräzision und Skalierung ein, erhält man:

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

Der Datentyp von 1. / 7 ist also numeric(12, 11) . Dieser Wert wird dann in float umgewandelt wie angefordert und als 0.14285714285 angezeigt (mit 11 Nachkommastellen).

Ohne einfache Parametrierung

Wenn keine einfache Parametrierung durchgeführt wird, wird der 1. Literal wird als numeric(1,0) eingegeben wie vorher. Die 7 wird anfänglich als integer eingegeben auch wie vorher gesehen. Der Hauptunterschied ist die integer wird in numeric(1,0) umgewandelt , also hat der Divisionsoperator gemeinsame Typen, mit denen er arbeiten kann. Dies ist die kleinste Genauigkeit und Skalierung, die den Wert 7 enthalten kann . Erinnern Sie sich an die einfache Parametrisierung, die numeric(10,0) verwendet wird hier.

Die Genauigkeits- und Skalierungsformeln zum Teilen von numeric(1,0) durch numeric(1,0) Geben Sie einen Ergebnisdatentyp von numeric(7,6) an :

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

Nach der endgültigen Umwandlung in float , ist das angezeigte Ergebnis 0.142857 (mit sechs Nachkommastellen).

Der beobachtete Unterschied in den Ergebnissen ist daher auf eine zwischenzeitliche Typableitung zurückzuführen (numeric(12,11) vs. numeric(7,6) ) und nicht die endgültige Umwandlung in float .

Wenn Sie weitere Beweise benötigen, die Umwandlung in float ist nicht verantwortlich, beachten Sie:

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

Ergebnis mit Dezimalzahl

Die Ergebnisse unterscheiden sich nach wie vor in Wert und Umfang.

Dieser Abschnitt behandelt nicht jede Eigenart der Datentypinferenz und -konvertierung mit einfacher Parametrisierung auf jeden Fall. Wie bereits erwähnt, ist es besser, wo immer möglich, explizite Parameter mit bekannten Datentypen zu verwenden.

Ende von Teil 2

Der nächste Teil dieser Serie beschreibt, wie einfache Parametrierung wirkt sich auf Ausführungspläne aus.