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

Einfache Parametrisierung und triviale Pläne – Teil 3

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üssen
EXECUTE 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;