SQL Server - Jeśli logika w procedurze przechowywanej i pamięci podręcznej planu

15

SQL Server 2012 i 2016 Standard:

Jeśli wstawię if-elselogikę do procedury składowanej, aby wykonać jedną z dwóch gałęzi kodu, w zależności od wartości parametru, czy silnik buforuje najnowszą wersję?

A jeśli w następnym wykonaniu wartość parametru ulegnie zmianie, czy ponownie skompiluje i ponownie buforuje procedurę przechowywaną , ponieważ należy wykonać inną gałąź kodu? (To zapytanie jest dość drogie.)

Nicole G.
źródło

Odpowiedzi:

27

SQL Server 2012 i 2016 Standard: Jeśli wstawię logikę if-else do procedury składowanej w celu wykonania jednej z dwóch gałęzi kodu, w zależności od wartości parametru, czy silnik buforuje najnowszą wersję?

Nie, buforuje wszystkie wersje. A raczej buforuje jedną wersję ze wszystkimi zbadanymi ścieżkami , skompilowanymi z przekazanymi zmiennymi.

Oto szybkie demo z wykorzystaniem bazy danych przepełnienia stosu.

Utwórz indeks:

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

Utwórz procedurę składowaną z podpowiedzią indeksu wskazującą na indeks, który nie istnieje, w kodzie rozgałęzionym.

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;

Jeśli wykonam zapisany proc, szukając reputacji = 1, pojawia się błąd.

EXEC dbo.YourMom @Reputation = 1;

Msg 308, poziom 16, stan 1, procedura YourMom, wiersz 14 [wiersz partii wsadowej 32] Indeks „ix_yourdad” w tabeli „dbo.Users” (określony w klauzuli FROM) nie istnieje.

Jeśli naprawimy nazwę indeksu i ponownie uruchomimy kwerendę, buforowany plan wygląda następująco:

Orzechy

Wewnątrz XML będzie miał dwa odwołania do @Reputationzmiennej.

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

Nieco prostszym testem byłoby po prostu oszacowanie planu dla przechowywanego proc. Optymalizator pokazuje obie ścieżki:

Orzechy

A jeśli w następnym wykonaniu wartość parametru ulegnie zmianie, czy ponownie skompiluje i ponownie buforuje procedurę przechowywaną, ponieważ należy wykonać inną gałąź kodu? (To zapytanie jest dość drogie do skompilowania.) Dziękujemy.

Nie, zachowa wartość czasu wykonywania pierwszej kompilacji.

Jeśli wykonamy ponownie z innym @Reputation:

EXEC dbo.YourMom @Reputation = 2;

Z aktualnego planu :

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

Nadal mamy skompilowaną wartość 1, ale teraz wartość czasu wykonania 2.

W pamięci podręcznej planu, którą możesz sprawdzić za pomocą bezpłatnego narzędzia, takiego jak to, które rozwija moja firma, sp_BlitzCache :

Orzechy

Procedura składowana została wywołana dwukrotnie, a każda instrukcja w niej wywołana została raz.

Więc co mamy? Jeden buforowany plan dla obu zapytań w procedurze przechowywanej.

Jeśli chcesz tego rodzaju rozgałęzionej logiki, musisz wywołać procedury składowane w trybie podrzędnym:

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;

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

Mam nadzieję że to pomoże!

Erik Darling
źródło