SQL Server: se la logica è nella procedura memorizzata e nella cache del piano


15

Standard di SQL Server 2012 e 2016:

Se inserisco la if-elselogica in una procedura memorizzata per eseguire uno dei due rami del codice, a seconda del valore di un parametro, il motore memorizza nella cache l'ultima versione?

E se sulla seguente esecuzione, il valore del parametro cambia, ricompilerà e ricodificherà la procedura memorizzata , dal momento che è necessario eseguire un ramo diverso del codice? (Questa query è piuttosto costosa da compilare.)

Risposte:


27

SQL Server 2012 e 2016 Standard: se inserisco la logica if-else in una procedura memorizzata per eseguire uno dei due rami del codice, a seconda del valore di un parametro, il motore memorizza nella cache l'ultima versione?

No, memorizza nella cache tutte le versioni. O meglio, memorizza nella cache una versione con tutti i percorsi esplorati, compilati con variabili passate.

Ecco una breve demo, usando il database Stack Overflow.

Crea un indice:

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

Creare una procedura memorizzata con un suggerimento indice che punta a un indice che non esiste, nel codice ramificato.

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;

Se eseguo quel proc memorizzato cercando Reputation = 1, ottengo un errore.

EXEC dbo.YourMom @Reputation = 1;

Messaggio 308, livello 16, stato 1, procedura YourMom, riga 14 [Batch Start Line 32] L'indice "ix_yourdad" nella tabella "dbo.Users" (specificato nella clausola FROM) non esiste.

Se ripariamo il nome dell'indice ed eseguiamo nuovamente la query, il piano memorizzato nella cache è simile al seguente:

Noccioline

All'interno, l'XML avrà due riferimenti alla @Reputationvariabile.

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

Un test leggermente più semplice sarebbe quello di ottenere solo un piano stimato per il proc memorizzato. Puoi vedere l'ottimizzatore che esplora entrambi i percorsi:

Noccioline

E se sulla seguente esecuzione, il valore del parametro cambia, ricompilerà e ricodificherà la procedura memorizzata, perché deve essere eseguito un ramo diverso del codice? (Questa query è piuttosto costosa da compilare.) Grazie.

No, manterrà il valore di runtime della prima compilation.

Se eseguiamo nuovamente l'esecuzione con un diverso @Reputation:

EXEC dbo.YourMom @Reputation = 2;

Dal piano attuale :

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

Abbiamo ancora un valore compilato di 1, ma ora un valore di runtime di 2.

Nella cache del piano, che puoi verificare con uno strumento gratuito come quello sviluppato dalla mia azienda, sp_BlitzCache :

Noccioline

La procedura memorizzata è stata chiamata due volte e ogni istruzione in essa è stata chiamata una volta.

Allora, cosa abbiamo? Un piano memorizzato nella cache per entrambe le query nella procedura memorizzata.

Se si desidera questo tipo di logica ramificata, è necessario chiamare le procedure memorizzate secondarie:

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;

O SQL dinamico:

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;

Spero che sia di aiuto!

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.