SQL Server - Wenn Logik in der gespeicherten Prozedur und im Plan-Cache

15

SQL Server 2012 und 2016 Standard:

Wenn ich if-elseLogik in eine gespeicherte Prozedur einbaue, um abhängig vom Wert eines Parameters einen von zwei Codezweigen auszuführen, speichert die Engine dann die neueste Version im Cache?

Und wenn sich bei der folgenden Ausführung der Wert des Parameters ändert, wird die gespeicherte Prozedur erneut kompiliert und zwischengespeichert , da ein anderer Zweig des Codes ausgeführt werden muss? (Diese Abfrage ist recht aufwendig zu kompilieren.)

Nicole G.
quelle

Antworten:

27

SQL Server 2012 und 2016 Standard: Wenn ich die if-else-Logik in eine gespeicherte Prozedur setze, um abhängig vom Wert eines Parameters einen von zwei Codezweigen auszuführen, speichert das Modul die neueste Version im Cache?

Nein, es werden alle Versionen zwischengespeichert. Besser gesagt, es wird eine Version mit allen erkannten Pfaden zwischengespeichert , die mit übergebenen Variablen kompiliert wurden.

Hier ist eine kurze Demo mit der Stack Overflow-Datenbank.

Erstellen Sie einen Index:

CREATE INDEX ix_yourmom ON dbo.Users (Reputation) INCLUDE (Id, DisplayName);
GO 

Erstellen Sie eine gespeicherte Prozedur mit einem Indexhinweis, der auf einen nicht vorhandenen Index in verzweigtem Code verweist.

CREATE OR ALTER PROCEDURE dbo.YourMom (@Reputation INT)
AS 
BEGIN

    IF @Reputation = 1
    BEGIN
        SELECT u.Id, u.DisplayName, u.Reputation
        FROM dbo.Users AS u WITH (INDEX = PK_Users_Id)
        WHERE u.Reputation = @Reputation;
    END;

    IF @Reputation > 1
    BEGIN
        SELECT u.Id, u.DisplayName, u.Reputation
        FROM dbo.Users AS u WITH (INDEX = ix_yourdad)
        WHERE u.Reputation = @Reputation;

    END;

END;

Wenn ich diesen gespeicherten Prozess auf der Suche nach Reputation = 1 ausführe, erhalte ich eine Fehlermeldung.

EXEC dbo.YourMom @Reputation = 1;

Meldung 308, Ebene 16, Status 1, Prozedur YourMom, Zeile 14 [Stapelstartzeile 32] Der Index 'ix_yourdad' für die Tabelle 'dbo.Users' (in der FROM-Klausel angegeben) ist nicht vorhanden.

Wenn wir den Indexnamen korrigieren und die Abfrage erneut ausführen, sieht der zwischengespeicherte Plan folgendermaßen aus:

Nüsse

In der XML-Datei befinden sich zwei Verweise auf die @ReputationVariable.

<ColumnReference Column="@Reputation" ParameterDataType="int" ParameterCompiledValue="(1)" />

Ein etwas einfacherer Test wäre, einfach einen geschätzten Plan für den gespeicherten Prozess zu erhalten. Sie können sehen, dass der Optimierer beide Pfade untersucht:

Nüsse

Und wenn sich bei der folgenden Ausführung der Wert des Parameters ändert, wird die gespeicherte Prozedur erneut kompiliert und zwischengespeichert, da ein anderer Zweig des Codes ausgeführt werden muss? (Diese Abfrage ist recht aufwendig zu kompilieren.) Vielen Dank.

Nein, der Laufzeitwert der ersten Kompilierung bleibt erhalten.

Wenn wir mit einem anderen Befehl erneut ausführen @Reputation:

EXEC dbo.YourMom @Reputation = 2;

Aus dem aktuellen Plan :

<ColumnReference Column="@Reputation" ParameterDataType="int" ParameterCompiledValue="(1)" ParameterRuntimeValue="(2)" />

Wir haben noch einen kompilierten Wert von 1, aber jetzt einen Laufzeitwert von 2.

Im Plan-Cache, den Sie mit einem kostenlosen Tool wie dem von meinem Unternehmen entwickelten testen können , sp_BlitzCache :

Nüsse

Die gespeicherte Prozedur wurde zweimal aufgerufen und jede Anweisung darin wurde einmal aufgerufen.

Also, was haben wir? Ein zwischengespeicherter Plan für beide Abfragen in der gespeicherten Prozedur.

Wenn Sie diese Art von Verzweigungslogik möchten , müssen Sie gespeicherte Prozeduren aufrufen:

CREATE OR ALTER PROCEDURE dbo.YourMom (@Reputation INT)
AS 
BEGIN

    IF @Reputation = 1
    BEGIN

        EXEC dbo.Reputation1Query;

    END;

    IF @Reputation > 1
    BEGIN

        EXEC dbo.ReputationGreaterThan1Query;

    END;

END;

Oder dynamisches SQL:

DECLARE @sql NVARCHAR(MAX) = N''

SET @sql +=
N'
SELECT u.Id, u.DisplayName, u.Reputation
        FROM dbo.Users AS u '
IF @Reputation = 1
BEGIN
    SET @sql += N' (INDEX = PK_Users_Id)
        WHERE u.Reputation = @Reputation;'
END;


IF @Reputation > 1 
BEGIN

SET @sql += ' WITH (INDEX = ix_yourmom)
        WHERE u.Reputation = @Reputation;'

END;


EXEC sys.sp_executesql @sql;

Hoffe das hilft!

Erik Darling
quelle