SQL Server - Si la logique dans la procédure stockée et le cache de plan

15

SQL Server 2012 et 2016 Standard:

Si je mets de la if-elselogique dans une procédure stockée pour exécuter l'une des deux branches de code, selon la valeur d'un paramètre, le moteur met-il en cache la dernière version?

Et si lors de l'exécution suivante, la valeur du paramètre change, va-t-il recompiler et remettre en cache la procédure stockée , car une branche différente du code doit être exécutée? (Cette requête est assez coûteuse à compiler.)

Nicole G.
la source

Réponses:

27

SQL Server 2012 et 2016 Standard: si je mets une logique if-else dans une procédure stockée pour exécuter l'une des deux branches de code, selon la valeur d'un paramètre, le moteur met-il en cache la dernière version?

Non, il met en cache toutes les versions. Ou plutôt, il met en cache une version avec tous les chemins explorés, compilés avec des variables passées en.

Voici une démonstration rapide, en utilisant la base de données Stack Overflow.

Créez un index:

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

Créez une procédure stockée avec une indication d'index qui pointe vers un index qui n'existe pas, en code ramifié.

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;

Si j'exécute ce proc stocké à la recherche de Réputation = 1, j'obtiens une erreur.

EXEC dbo.YourMom @Reputation = 1;

Msg 308, niveau 16, état 1, procédure YourMom, ligne 14 [ligne de démarrage par lots 32] Index 'ix_yourdad' sur la table 'dbo.Users' (spécifié dans la clause FROM) n'existe pas.

Si nous corrigeons le nom d'index et réexécutons la requête, le plan mis en cache ressemble à ceci:

Des noisettes

A l'intérieur, le XML aura deux références à la @Reputationvariable.

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

Un test un peu plus simple serait d'obtenir simplement un plan estimé pour le proc stocké. Vous pouvez voir l'optimiseur explorer les deux chemins:

Des noisettes

Et si lors de l'exécution suivante, la valeur du paramètre change, va-t-il recompiler et remettre en cache la procédure stockée, car une branche différente du code doit être exécutée? (Cette requête est assez coûteuse à compiler.) Merci.

Non, il conservera la valeur d'exécution de la première compilation.

Si nous réexécutons avec un autre @Reputation:

EXEC dbo.YourMom @Reputation = 2;

Du plan réel :

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

Nous avons toujours une valeur compilée de 1, mais maintenant une valeur d'exécution de 2.

Dans le cache du plan, que vous pouvez consulter avec un outil gratuit comme celui développé par ma société, sp_BlitzCache :

Des noisettes

La procédure stockée a été appelée deux fois et chaque instruction qu'elle contient a été appelée une fois.

Alors qu'est-ce que nous avons? Un plan mis en cache pour les deux requêtes dans la procédure stockée.

Si vous voulez ce type de logique ramifiée, vous devez appeler des procédures sous-stockées:

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 dynamique:

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;

J'espère que cela t'aides!

Erik Darling
la source