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.