SQL Server - Se a lógica no procedimento armazenado e o cache do plano

15

SQL Server 2012 e 2016 Standard:

Se eu colocar a if-elselógica em um procedimento armazenado para executar uma das duas ramificações do código, dependendo do valor de um parâmetro, o mecanismo armazenará em cache a versão mais recente?

E se, na execução a seguir, o valor do parâmetro for alterado, ele será recompilado e armazenado em cache novamente o procedimento armazenado , pois uma ramificação diferente do código deve ser executada? (Essa consulta é bastante cara para compilar.)

Nicole G.
fonte

Respostas:

27

SQL Server 2012 e 2016 Standard: Se eu colocar a lógica if-else em um procedimento armazenado para executar uma das duas ramificações do código, dependendo do valor de um parâmetro, o mecanismo armazenará em cache a versão mais recente?

Não, ele armazena em cache todas as versões. Ou melhor, ele armazena em cache uma versão com todos os caminhos explorados, compilados com as variáveis ​​passadas.

Aqui está uma demonstração rápida, usando o banco de dados Stack Overflow.

Crie um índice:

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

Crie um procedimento armazenado com uma dica de índice que aponte para um índice que não existe, no código ramificado.

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 eu executar esse processo armazenado procurando Reputação = 1, recebo um erro.

EXEC dbo.YourMom @Reputation = 1;

Msg 308, Nível 16, Estado 1, Procedimento YourMom, Linha 14 [Linha Inicial do Lote 32] O índice 'ix_seu pai' na tabela 'dbo.Users' (especificado na cláusula FROM) não existe.

Se corrigirmos o nome do índice e executarmos novamente a consulta, o plano em cache ficará assim:

Nozes

Dentro, o XML terá duas referências à @Reputationvariável.

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

Um teste um pouco mais simples seria apenas obter um plano estimado para o processo armazenado. Você pode ver o otimizador explorando os dois caminhos:

Nozes

E se na execução a seguir o valor do parâmetro for alterado, ele será recompilado e armazenado em cache novamente o procedimento armazenado, porque uma ramificação diferente do código deve ser executada? (Essa consulta é bastante cara para compilar.) Obrigado.

Não, ele manterá o valor de tempo de execução da primeira compilação.

Se executarmos novamente com um diferente @Reputation:

EXEC dbo.YourMom @Reputation = 2;

Do plano real :

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

Ainda temos um valor compilado de 1, mas agora um valor de tempo de execução de 2.

No cache do plano, que você pode conferir com uma ferramenta gratuita como a que minha empresa desenvolve, sp_BlitzCache :

Nozes

O procedimento armazenado foi chamado duas vezes e cada instrução nele foi chamada uma vez.

O que temos então? Um plano em cache para as duas consultas no procedimento armazenado.

Se você deseja esse tipo de lógica ramificada, precisará chamar procedimentos sub-armazenados:

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;

Ou SQL dinâmico:

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;

Espero que isto ajude!

Erik Darling
fonte