Ausführungspläne
Wenn eine SQL-Anweisung einfache Parametrisierung verwendet, ist es komplizierter, als Sie den Informationen in Ausführungsplänen entnehmen könnten . Es ist keine Überraschung, dass selbst sehr erfahrene SQL Server-Benutzer dazu neigen, dies angesichts der widersprüchlichen Informationen, die uns oft zur Verfügung gestellt werden, falsch zu verstehen.
Sehen wir uns einige Beispiele an, die die Stack Overflow 2010-Datenbank auf SQL Server 2019 CU 14 verwenden, wobei die Datenbankkompatibilität auf 150 eingestellt ist.
Zunächst benötigen wir einen neuen Nonclustered-Index:
CREATE INDEX [IX dbo.Users Reputation (DisplayName)] ON dbo.Users (Reputation) INCLUDE (DisplayName);
1. Einfache Parametrierung angewendet
Diese erste Beispielabfrage verwendet einfache Parametrisierung :
SELECT U.DisplayName FROM dbo.Users AS U WHERE U.Reputation = 999;
Die geschätzte (Vorausführungs-)Plan hat die folgenden parametrisierungsbezogenen Elemente:
Geschätzte Planparametrisierungseigenschaften
Beachten Sie den @1
-Parameter wird überall eingeführt, mit Ausnahme des Abfragetexts, der ganz oben angezeigt wird.
Das eigentliche (nach Ausführung) Plan hat:
Tatsächliche Planparametrisierungseigenschaften
Beachten Sie, dass das Eigenschaftsfenster jetzt den ParameterizedText
verloren hat Element, während Informationen über den Laufzeitwert des Parameters gewonnen werden. Der parametrisierte Abfragetext wird nun im oberen Bereich des Fensters mit „@1
angezeigt “ statt „999“.
2. Einfache Parametrierung nicht angewendet
Dieses zweite Beispiel tut dies nicht einfache Parametrisierung verwenden:
-- Projecting an extra column SELECT U.DisplayName, U.CreationDate -- NEW FROM dbo.Users AS U WHERE U.Reputation = 999;
Die geschätzte Plan zeigt:
Geschätzter nicht parametrisierter Plan
Diesmal der Parameter @1
fehlt in der Indexsuche Tooltip, aber der parametrisierte Text und andere Elemente der Parameterliste sind die gleichen wie zuvor.
Schauen wir uns das tatsächliche an Ausführungsplan:
Tatsächlicher nicht parametrisierter Plan
Die Ergebnisse sind die gleichen wie beim vorherigen parametrisierten actual Plan, außer jetzt die Indexsuche Tooltip zeigt den nicht parametrisierten Wert „999“ an. Der ganz oben angezeigte Abfragetext verwendet den @1
Parametermarkierung. Das Eigenschaftsfenster verwendet auch @1
und zeigt den Laufzeitwert des Parameters an.
Die Abfrage ist keine parametrisierte Anweisung trotz aller gegenteiligen Beweise.
3. Parametrierung fehlgeschlagen
Mein drittes Beispiel ist auch nicht vom Server parametriert:
-- LOWER function used SELECT U.DisplayName, LOWER(U.DisplayName) FROM dbo.Users AS U WHERE U.Reputation = 999;
Die geschätzte Plan ist:
Geschätzte Planparametrisierung fehlgeschlagen
Es wird kein @1
erwähnt Parameter jetzt überall und die Parameterliste Abschnitt des Eigenschaftsfensters fehlt.
Das eigentliche Ausführungsplan ist derselbe, also werde ich ihn nicht zeigen.
4. Paralleler parametrisierter Plan
Ich möchte Ihnen ein weiteres Beispiel mit Parallelität im Ausführungsplan zeigen. Die niedrigen geschätzten Kosten meiner Testabfragen bedeuten, dass wir die Kostenschwelle für Parallelität auf 1:
senken müssenEXECUTE sys.sp_configure @configname = 'cost threshold for parallelism', @configvalue = 1; RECONFIGURE;
Das Beispiel ist diesmal etwas komplexer:
SELECT U.DisplayName FROM dbo.Users AS U WHERE U.Reputation >= 5 AND U.DisplayName > N'ZZZ' ORDER BY U.Reputation DESC;
Die geschätzte Ausführungsplan ist:
Geschätzter parallel parametrisierter Plan
Der Abfragetext oben bleibt unparametrisiert, während alles andere es ist. Es gibt jetzt zwei Parametermarkierungen, @1
und @2
, weil einfache Parametrisierung zwei passende Literalwerte gefunden.
Das eigentliche Ausführungsplan folgt dem Muster von Beispiel 1:
Tatsächlicher parallel parametrisierter Plan
Der Abfragetext oben ist jetzt parametrisiert und das Eigenschaftenfenster enthält Laufzeitparameterwerte. Dieser parallele Plan (mit a Sortieren Operator) wird auf jeden Fall vom Server mittels einfacher Parametrisierung parametrisiert .
Zuverlässige Methoden
Es gibt Gründe für alle bisher gezeigten Verhaltensweisen und noch einige mehr. Ich werde versuchen, viele davon im nächsten Teil dieser Serie zu erklären, wenn ich über die Planerstellung spreche.
Inzwischen ist die Situation bei Showplan im Allgemeinen und SSMS im Besonderen alles andere als ideal. Es ist verwirrend für Leute, die ihre gesamte Karriere mit SQL Server gearbeitet haben. Welchen Parametermarkern vertrauen Sie und welche ignorieren Sie?
Es gibt mehrere zuverlässige Methoden, um festzustellen, ob eine einfache Parametrisierung erfolgreich auf eine bestimmte Anweisung angewendet wurde oder nicht.
Abfragespeicher
Ich beginne mit einem der bequemsten, dem Abfragespeicher. Leider ist es nicht immer so einfach, wie Sie sich das vielleicht vorstellen.
Sie müssen die Abfragespeicherfunktion für den Datenbankkontext aktivieren wo die Anweisung ausgeführt wird und der OPERATION_MODE
muss auf READ_WRITE
gesetzt werden , sodass der Abfragespeicher aktiv Daten sammeln kann.
Nachdem diese Bedingungen erfüllt sind, enthält die Showplan-Ausgabe nach der Ausführung zusätzliche Attribute, einschließlich des StatementParameterizationType . Wie der Name schon sagt, enthält dies einen Code, der die Art der Parametrisierung beschreibt, die für die Anweisung verwendet wird.
Es ist im SSMS-Eigenschaftenfenster sichtbar, wenn der Stammknoten eines Plans ausgewählt ist:
StatementParameterizationType
Die Werte sind in sys.query_store_query
dokumentiert :
- 0 – Keine
- 1 – Benutzer (explizite Parametrierung)
- 2 – Einfache Parametrierung
- 3 – Zwangsparametrierung
Dieses vorteilhafte Attribut erscheint in SSMS nur, wenn es tatsächlich ist Plan angefordert wird und fehlt, wenn ein geschätzter Plan ausgewählt ist. Es ist wichtig, daran zu denken, dass der Plan zwischengespeichert werden muss . Anfordern einer Schätzung Plan von SSMS speichert den erstellten Plan nicht zwischen (seit SQL Server 2012).
Nachdem der Plan zwischengespeichert wurde, wird der StatementParameterizationType erscheint an den üblichen Stellen, einschließlich über sys.dm_exec_query_plan
.
Sie können auch den anderen Stellen vertrauen, an denen der Parametrisierungstyp im Abfragespeicher aufgezeichnet ist, z. B. query_parameterization_type_desc
Spalte in sys.query_store_query
.
Eine wichtige Einschränkung. Wenn der Abfragespeicher OPERATION_MODE
auf READ_ONLY
gesetzt ist , der StatementParameterizationType -Attribut ist immer noch in SSMS actual ausgefüllt Pläne – aber es ist immer null – einen falschen Eindruck erweckend, dass die Anweisung nicht parametrisiert wurde, obwohl es durchaus hätte sein können.
Wenn Sie den Abfragespeicher gerne aktivieren, sicher sind, dass er schreibgeschützt ist, und sich nur die Pläne nach der Ausführung in SSMS ansehen, wird dies für Sie funktionieren.
Standardplan-Prädikate
Der oben im grafischen Showplan-Fenster in SSMS angezeigte Abfragetext ist nicht zuverlässig, wie die Beispiele gezeigt haben. Sie können sich auch nicht auf die ParameterList verlassen in den Eigenschaften angezeigt Fenster, wenn der Wurzelknoten des Plans ausgewählt ist. Der ParameterizedText Attribut, das für geschätzt angezeigt wird Pläne allein ist auch nicht abschließend.
Sie können sich jedoch auf die Eigenschaften der einzelnen Planbetreiber verlassen. Die angegebenen Beispiele zeigen, dass diese in den Tooltips vorhanden sind beim Bewegen der Maus über einen Operator.
Ein Prädikat, das eine Parametermarkierung wie @1
enthält oder @2
gibt einen parametrisierten Plan an. Die Operatoren, die am wahrscheinlichsten einen Parameter enthalten, sind Index Scan , Indexsuche und Filtern .
Prädikate mit Parametermarkierungen
Wenn die Nummerierung mit @1
beginnt verwendet es einfache Parametrisierung . Die Zwangsparametrierung beginnt mit @0
. Ich sollte erwähnen, dass das hier dokumentierte Nummerierungsschema jederzeit geändert werden kann:
Änderungswarnung
Trotzdem dies ist die Methode, die ich verwende am häufigsten, um festzustellen, ob ein Plan einer serverseitigen Parametrisierung unterzogen wurde. Es ist im Allgemeinen schnell und einfach, einen Plan visuell auf Prädikate zu überprüfen, die Parametermarkierungen enthalten. Diese Methode funktioniert auch für beide Arten von Plänen, geschätzt und tatsächlich .
Dynamische Verwaltungsobjekte
Es gibt mehrere Möglichkeiten, den Plancache und zugehörige DMOs abzufragen, um festzustellen, ob eine Anweisung parametrisiert wurde. Natürlich funktionieren diese Abfragen nur bei Plänen im Cache, daher muss die Anweisung vollständig ausgeführt, zwischengespeichert und nicht nachträglich aus irgendeinem Grund entfernt worden sein.
Der direkteste Weg ist, nach einem Adhoc zu suchen Planen Sie, indem Sie eine exakte SQL-Textübereinstimmung mit der Interessenerklärung verwenden. Der Adhoc Plan wird eine Hülle sein mit einem ParameterizedPlanHandle wenn die Anweisung vom Server parametrisiert wird. Das Plan-Handle wird dann verwendet, um Prepared zu lokalisieren planen. Ein Adhoc Plan existiert nicht, wenn die Optimierung für Ad-hoc-Workloads aktiviert ist und die betreffende Anweisung nur einmal ausgeführt wurde.
Diese Art von Anfrage führt häufig dazu, dass eine beträchtliche Menge an XML geschreddert und der gesamte Plan-Cache mindestens einmal gescannt wird. Es ist auch leicht, den Code falsch zu verstehen, nicht zuletzt, weil Pläne im Cache einen ganzen Stapel abdecken. Ein Stapel kann mehrere Anweisungen enthalten, von denen jede parametrisiert sein kann oder nicht. Nicht alle DMOs arbeiten mit der gleichen Granularität (Batch oder Statement), was es ziemlich einfach macht, sich zu lösen.
Eine effiziente Methode zum Auflisten von Interessenbekundungen zusammen mit Planfragmenten für genau diese einzelnen Bekundungen ist unten dargestellt:
SELECT StatementText = SUBSTRING(T.[text], 1 + (QS.statement_start_offset / 2), 1 + ((QS.statement_end_offset - QS.statement_start_offset) / 2)), IsParameterized = IIF(T.[text] LIKE N'(%', 'Yes', 'No'), query_plan = TRY_CONVERT(xml, P.query_plan) FROM sys.dm_exec_query_stats AS QS CROSS APPLY sys.dm_exec_sql_text (QS.[sql_handle]) AS T CROSS APPLY sys.dm_exec_text_query_plan ( QS.plan_handle, QS.statement_start_offset, QS.statement_end_offset) AS P WHERE -- Statements of interest T.[text] LIKE N'%DisplayName%Users%' -- Exclude queries like this one AND T.[text] NOT LIKE N'%sys.dm%' ORDER BY QS.last_execution_time ASC, QS.statement_start_offset ASC;
Lassen Sie uns zur Veranschaulichung einen einzelnen Stapel ausführen, der die vier Beispiele von früher enthält:
ALTER DATABASE SCOPED CONFIGURATION CLEAR PROCEDURE_CACHE; GO -- Example 1 SELECT U.DisplayName FROM dbo.Users AS U WHERE U.Reputation = 999; -- Example 2 SELECT U.DisplayName, U.CreationDate FROM dbo.Users AS U WHERE U.Reputation = 999; -- Example 3 SELECT U.DisplayName, LOWER(U.DisplayName) FROM dbo.Users AS U WHERE U.Reputation = 999; -- Example 4 SELECT U.DisplayName FROM dbo.Users AS U WHERE U.Reputation >= 5 AND U.DisplayName > N'ZZZ' ORDER BY U.Reputation DESC; GO
Die Ausgabe der DMO-Abfrage lautet:
DMO-Abfrageausgabe
Dies bestätigt, dass nur die Beispiele 1 und 4 erfolgreich parametriert wurden.
Leistungszähler
Es ist möglich, die Leistungsindikatoren von SQL Statistics zu verwenden, um einen detaillierten Einblick in die Parametrisierungsaktivität für beide geschätzt zu erhalten und tatsächlich Pläne. Die verwendeten Zähler gelten nicht pro Sitzung, daher müssen Sie eine Testinstanz ohne andere gleichzeitige Aktivität verwenden, um genaue Ergebnisse zu erhalten.
Ich werde die Parametrierungszählerinformationen mit Daten aus sys.dm_exec_query_optimizer_info
ergänzen DMO, um auch Statistiken zu trivialen Plänen bereitzustellen.
Es ist einige Sorgfalt erforderlich, um zu verhindern, dass Anweisungen, die die Zählerinformationen lesen, diese Zähler selbst ändern. Ich werde dies angehen, indem ich ein paar temporär gespeicherte Prozeduren erstelle:
CREATE PROCEDURE #TrivialPlans AS SET NOCOUNT ON; SELECT OI.[counter], OI.occurrence FROM sys.dm_exec_query_optimizer_info AS OI WHERE OI.[counter] = N'trivial plan'; GO CREATE PROCEDURE #PerfCounters AS SET NOCOUNT ON; SELECT PC.[object_name], PC.counter_name, PC.cntr_value FROM sys.dm_os_performance_counters AS PC WHERE PC.counter_name LIKE N'%Param%';
Das Skript zum Testen einer bestimmten Anweisung sieht dann so aus:
ALTER DATABASE SCOPED CONFIGURATION CLEAR PROCEDURE_CACHE; GO EXECUTE #PerfCounters; EXECUTE #TrivialPlans; GO SET SHOWPLAN_XML ON; GO -- The statement(s) under test: -- Example 3 SELECT U.DisplayName, LOWER(U.DisplayName) FROM dbo.Users AS U WHERE U.Reputation = 999; GO SET SHOWPLAN_XML OFF; GO EXECUTE #TrivialPlans; EXECUTE #PerfCounters;
Kommentieren Sie SHOWPLAN_XML
Batches aus, um die Zielanweisung(en) auszuführen und actual abzurufen Pläne. Lassen Sie sie geschätzt an Ort und Stelle Ausführungspläne.
Wenn Sie das Ganze wie geschrieben ausführen, erhalten Sie die folgenden Ergebnisse:
Ergebnisse des Leistungsindikatortests
Ich habe oben hervorgehoben, wo sich die Werte beim Testen von Beispiel 3 geändert haben.
Die Erhöhung des Zählers „Trivialplan“ von 1050 auf 1051 zeigt an, dass für die Testanweisung ein Trivialplan gefunden wurde.
Die einfachen Parametrierungszähler wurden sowohl für Versuche als auch für Fehler um 1 erhöht, was anzeigt, dass SQL Server versucht hat, die Anweisung zu parametrisieren, aber fehlgeschlagen ist.
Ende von Teil 3
Im nächsten Teil dieser Serie erkläre ich die merkwürdigen Dinge, die wir gesehen haben, indem ich beschreibe, wie einfache Parametrisierung und triviale Pläne mit dem Kompilierungsprozess interagieren.
Wenn Sie Ihren Kostenschwellenwert für Parallelität geändert haben Um die Beispiele auszuführen, denken Sie daran, es zurückzusetzen (meins war auf 50 gesetzt):
EXECUTE sys.sp_configure @configname = 'cost threshold for parallelism', @configvalue = 50; RECONFIGURE;