Database
 sql >> Base de données >  >> RDS >> Database

Renifleur de paramètres, incorporation et options RECOMPILE

Renifleur de paramètres

Le paramétrage des requêtes favorise la réutilisation des plans d'exécution mis en cache, évitant ainsi les compilations inutiles et réduisant le nombre de requêtes ad hoc dans le cache du plan.

Ce sont toutes de bonnes choses, à condition la requête paramétrée doit vraiment utiliser le même plan d'exécution mis en cache pour différentes valeurs de paramètres. Un plan d'exécution qui est efficace pour une valeur de paramètre peut ne pas être un bon choix pour d'autres valeurs de paramètres possibles.

Lorsque le reniflage des paramètres est activé (valeur par défaut), SQL Server choisit un plan d'exécution basé sur les valeurs de paramètre particulières qui existent au moment de la compilation. L'hypothèse implicite est que les instructions paramétrées sont le plus souvent exécutées avec les valeurs de paramètre les plus courantes. Cela semble assez raisonnable (voire évident) et en effet cela fonctionne souvent bien.

Un problème peut survenir lors d'une recompilation automatique du plan mis en cache. Une recompilation peut être déclenchée pour toutes sortes de raisons, par exemple parce qu'un index utilisé par le plan mis en cache a été supprimé (une exactitude recompilation) ou parce que les informations statistiques ont changé (une optimalité recompiler).

Quelle que soit la cause exacte de la recompilation du plan, il est possible qu'un élément atypique La valeur est transmise en tant que paramètre au moment où le nouveau plan est généré. Cela peut entraîner un nouveau plan mis en cache (basé sur la valeur de paramètre atypique sniffée) qui n'est pas bon pour la majorité des exécutions pour lesquelles il sera réutilisé.

Il n'est pas facile de prédire quand un plan d'exécution particulier sera recompilé (par exemple, parce que les statistiques ont suffisamment changé), ce qui entraîne une situation où un plan réutilisable de bonne qualité peut être soudainement remplacé par un plan assez différent optimisé pour des valeurs de paramètres atypiques.

Un tel scénario se produit lorsque la valeur atypique est hautement sélective, ce qui se traduit par un plan optimisé pour un petit nombre de lignes. Ces plans utilisent souvent une exécution à thread unique, des jointures de boucles imbriquées et des recherches. De graves problèmes de performances peuvent survenir lorsque ce plan est réutilisé pour différentes valeurs de paramètres qui génèrent un nombre beaucoup plus important de lignes.

Désactivation de la détection des paramètres

Le reniflage de paramètres peut être désactivé à l'aide de l'indicateur de trace documenté 4136. L'indicateur de trace est également pris en charge pour par requête utilisation via le QUERYTRACEON indice de requête. Les deux s'appliquent à partir de SQL Server 2005 Service Pack 4 (et légèrement plus tôt si vous appliquez des mises à jour cumulatives au Service Pack 3).

À partir de SQL Server 2016, le reniflage de paramètres peut également être désactivé au niveau de la base de données , en utilisant le PARAMETER_SNIFFING argument de ALTER DATABASE SCOPED CONFIGURATION .

Lorsque le reniflage des paramètres est désactivé, SQL Server utilise la distribution moyenne statistiques pour choisir un plan d'exécution.

Cela semble également être une approche raisonnable (et peut aider à éviter la situation où le plan est optimisé pour une valeur de paramètre inhabituellement sélective), mais ce n'est pas non plus une stratégie parfaite :un plan optimisé pour une valeur « moyenne » pourrait bien finir par être sérieusement sous-optimal pour les valeurs de paramètres couramment observées.

Considérez un plan d'exécution qui contient des opérateurs gourmands en mémoire comme les tris et les hachages. Étant donné que la mémoire est réservée avant le début de l'exécution de la requête, un plan paramétré basé sur des valeurs de distribution moyennes peut déborder sur tempdb pour les valeurs de paramètre communes qui produisent plus de données que prévu par l'optimiseur.

Les réservations de mémoire ne peuvent généralement pas augmenter pendant l'exécution de la requête, quelle que soit la quantité de mémoire disponible dont dispose le serveur. Certaines applications bénéficient de la désactivation du reniflage des paramètres (voir cet article d'archive par l'équipe Dynamics AX Performance pour un exemple).

Pour la plupart des charges de travail, désactiver entièrement le reniflage des paramètres est la mauvaise solution , et peut même être un désastre. Le reniflage de paramètres est une optimisation heuristique :il fonctionne mieux que l'utilisation de valeurs moyennes sur la plupart des systèmes, la plupart du temps.

Indices de requête

SQL Server fournit une gamme d'indicateurs de requête et d'autres options pour ajuster le comportement du reniflage des paramètres :

  • Le OPTIMIZE FOR (@parameter = value) l'indicateur de requête crée un plan réutilisable basé sur une valeur spécifique.
  • OPTIMIZE FOR (@parameter UNKNOWN) utilise des statistiques de distribution moyenne pour un paramètre particulier.
  • OPTIMIZE FOR UNKNOWN utilise la distribution moyenne pour tous les paramètres (même effet que l'indicateur de trace 4136).
  • Le WITH RECOMPILE L'option de procédure stockée compile un nouveau plan de procédure pour chaque exécution.
  • L'OPTION (RECOMPILE) l'indicateur de requête compile un nouveau plan pour une instruction individuelle.

L'ancienne technique de "masquage des paramètres" (attribuer des paramètres de procédure à des variables locales et référencer les variables à la place) a le même effet que de spécifier OPTIMIZE FOR UNKNOWN . Il peut être utile sur les instances antérieures à SQL Server 2008 (le OPTIMIZE FOR l'indice était nouveau pour 2008).

On pourrait faire valoir que chaque L'instruction paramétrée doit être vérifiée pour sa sensibilité aux valeurs des paramètres, et laissée seule (si le comportement par défaut fonctionne bien) ou explicitement indiquée à l'aide de l'une des options ci-dessus.

Cela est rarement fait dans la pratique, en partie parce que la réalisation d'une analyse complète de toutes les valeurs de paramètres possibles peut prendre du temps et nécessite des compétences assez avancées.
Le plus souvent, aucune analyse de ce type n'est effectuée et les problèmes de sensibilité aux paramètres sont traités au fur et à mesure. lorsqu'ils surviennent en production.

Ce manque d'analyse préalable est probablement l'une des principales raisons pour lesquelles le reniflage de paramètres a une mauvaise réputation. Il est utile d'être conscient du potentiel de problèmes et d'effectuer au moins une analyse rapide des instructions susceptibles de causer des problèmes de performances lorsqu'elles sont recompilées avec une valeur de paramètre atypique.

Qu'est-ce qu'un paramètre ?

Certains diront qu'un SELECT l'instruction référençant une variable locale est une "instruction paramétrée" en quelque sorte, mais ce n'est pas la définition qu'utilise SQL Server.

Une indication raisonnable qu'une déclaration utilise des paramètres peut être trouvée en regardant les propriétés du plan (voir les Paramètres onglet dans Sentry One Plan Explorer. Ou cliquez sur le nœud racine du plan de requête dans SSMS, ouvrez les Propriétés fenêtre et développez la Liste des paramètres nœud):

La « valeur compilée » affiche la valeur reniflée du paramètre utilisé pour compiler le plan mis en cache. La « valeur d'exécution » indique la valeur du paramètre sur l'exécution particulière capturée dans le plan.

L'une ou l'autre de ces propriétés peut être vide ou manquante dans différentes circonstances. Si une requête n'est pas paramétrée, les propriétés seront tout simplement toutes manquantes.

Tout simplement parce que rien n'est jamais simple dans SQL Server, il existe des situations où la liste de paramètres peut être remplie, mais l'instruction n'est toujours pas paramétrée. Cela peut se produire lorsque SQL Server tente un paramétrage simple (discuté plus tard) mais décide que la tentative est « non sécurisée ». Dans ce cas, des marqueurs de paramètres seront présents, mais le plan d'exécution n'est en fait pas paramétré.

Sniffing n'est pas seulement pour les procédures stockées

Le reniflage de paramètres se produit également lorsqu'un lot est explicitement paramétré pour être réutilisé à l'aide de sp_executesql .

Par exemple :

EXECUTE sys.sp_executesql
    N'
    SELECT
        P.ProductID,
        P.Name,
        TotalQty = SUM(TH.Quantity)
    FROM Production.Product AS P
    JOIN Production.TransactionHistory AS TH
        ON TH.ProductID = P.ProductID
    WHERE
        P.Name LIKE @NameLike
    GROUP BY
        P.ProductID,
        P.Name;
    ',
    N'@NameLike nvarchar(50)',
    @NameLike = N'K%';

L'optimiseur choisit un plan d'exécution basé sur la valeur reniflée du @NameLike paramètre. On estime que la valeur du paramètre "K %" correspond à très peu de lignes dans le Product table, afin que l'optimiseur choisisse une jointure de boucle imbriquée et une stratégie de recherche de clé :

Exécuter à nouveau l'instruction avec une valeur de paramètre de "[H-R]%" (qui correspondra à beaucoup plus de lignes) réutilise le plan paramétré mis en cache :

EXECUTE sys.sp_executesql
    N'
    SELECT
        P.ProductID,
        P.Name,
        TotalQty = SUM(TH.Quantity)
    FROM Production.Product AS P
    JOIN Production.TransactionHistory AS TH
        ON TH.ProductID = P.ProductID
    WHERE
        P.Name LIKE @NameLike
    GROUP BY
        P.ProductID,
        P.Name;
    ',
    N'@NameLike nvarchar(50)',
    @NameLike = N'[H-R]%';

AdventureWorks L'exemple de base de données est trop petit pour en faire un désastre de performances, mais ce plan n'est certainement pas optimal pour la deuxième valeur de paramètre.

Nous pouvons voir le plan que l'optimiseur aurait choisi en vidant le cache du plan et en exécutant à nouveau la deuxième requête :

Avec un plus grand nombre de correspondances attendues, l'optimiseur détermine qu'une jointure par hachage et une agrégation par hachage sont de meilleures stratégies.

Fonctions T-SQL

Le reniflage de paramètres se produit également avec les fonctions T-SQL, bien que la façon dont les plans d'exécution sont générés puisse rendre cela plus difficile à voir.

Il y a de bonnes raisons d'éviter les fonctions scalaires et multi-instructions T-SQL en général, donc à des fins éducatives uniquement, voici une version de fonction table multi-instructions T-SQL de notre requête de test :

CREATE FUNCTION dbo.F
    (@NameLike nvarchar(50))
RETURNS @Result TABLE
(
    ProductID   integer NOT NULL PRIMARY KEY,
    Name        nvarchar(50) NOT NULL,
    TotalQty    integer NOT NULL
)
WITH SCHEMABINDING
AS
BEGIN
    INSERT @Result
    SELECT
        P.ProductID,
        P.Name,
        TotalQty = SUM(TH.Quantity)
    FROM Production.Product AS P
    JOIN Production.TransactionHistory AS TH
        ON TH.ProductID = P.ProductID
    WHERE
        P.Name LIKE @NameLike
    GROUP BY
        P.ProductID,
        P.Name;
 
    RETURN;
END;

La requête suivante utilise la fonction pour afficher des informations sur les noms de produits commençant par "K" :

SELECT
    Result.ProductID,
    Result.Name,
    Result.TotalQty
FROM dbo.F(N'K%') AS Result;

Il est plus difficile de voir le reniflage des paramètres avec une fonction intégrée, car SQL Server ne renvoie pas de plan de requête post-exécution (réel) distinct pour chaque appel de fonction. La fonction pourrait être appelée plusieurs fois dans une seule instruction, et les utilisateurs ne seraient pas impressionnés si SSMS essayait d'afficher un million de plans d'appel de fonction pour une seule requête.

À la suite de cette décision de conception, le plan réel renvoyé par SQL Server pour notre requête de test n'est pas très utile :

Néanmoins, il existe façons de voir le reniflage de paramètres en action avec des fonctions intégrées. La méthode que j'ai choisi d'utiliser ici consiste à inspecter le cache du plan :

SELECT
    DEQS.plan_generation_num,
    DEQS.execution_count,
    DEQS.last_logical_reads,
    DEQS.last_elapsed_time,
    DEQS.last_rows,
    DEQP.query_plan
FROM sys.dm_exec_query_stats AS DEQS
CROSS APPLY sys.dm_exec_sql_text(DEQS.plan_handle) AS DEST
CROSS APPLY sys.dm_exec_query_plan(DEQS.plan_handle) AS DEQP
WHERE
    DEST.objectid = OBJECT_ID(N'dbo.F', N'TF');

Ce résultat montre que le plan de fonction a été exécuté une fois, au prix de 201 lectures logiques avec un temps écoulé de 2891 microsecondes, et que l'exécution la plus récente a renvoyé une ligne. La représentation du plan XML renvoyée montre que la valeur du paramètre était reniflé :

Maintenant, exécutez à nouveau l'instruction, avec un paramètre différent :

SELECT
    Result.ProductID,
    Result.Name,
    Result.TotalQty
FROM dbo.F(N'[H-R]%') AS Result;

Le plan de post-exécution montre que 306 lignes ont été renvoyées par la fonction :

La requête de cache de plan montre que le plan d'exécution mis en cache pour la fonction a été réutilisé (execution_count =2):

Il montre également un nombre beaucoup plus élevé de lectures logiques et un temps écoulé plus long par rapport à l'exécution précédente. Cela est cohérent avec la réutilisation de boucles imbriquées et d'un plan de recherche, mais pour être complètement sûr, le plan de fonction post-exécution peut être capturé à l'aide d'Événements étendus ou le SQL Server Profiler outil :

Étant donné que le reniflage de paramètres s'applique aux fonctions, ces modules peuvent souffrir des mêmes changements de performances inattendus généralement associés aux procédures stockées.

Par exemple, la première fois qu'une fonction est référencée, un plan peut être mis en cache qui n'utilise pas le parallélisme. Les exécutions ultérieures avec des valeurs de paramètres qui bénéficieraient du parallélisme (mais réutiliseraient le plan série mis en cache) afficheront des performances étonnamment médiocres.

Ce problème peut être difficile à identifier car SQL Server ne renvoie pas de plans de post-exécution distincts pour les appels de fonction, comme nous l'avons vu. Utilisation des événements étendus ou Profiler capturer régulièrement des plans post-exécution peut être extrêmement gourmand en ressources, il est donc souvent logique d'utiliser cette technique de manière très ciblée. Les difficultés liées au débogage des problèmes de sensibilité des paramètres de fonction signifient qu'il est encore plus intéressant de faire une analyse (et de coder de manière défensive) avant que la fonction n'entre en production.

Le reniflage de paramètres fonctionne exactement de la même manière avec les fonctions scalaires T-SQL définies par l'utilisateur (sauf si elles sont en ligne, sur SQL Server 2019 et versions ultérieures). Les fonctions table en ligne ne génèrent pas de plan d'exécution séparé pour chaque appel, car (comme leur nom l'indique) elles sont intégrées dans la requête appelante avant la compilation.

Méfiez-vous des NULL reniflés

Videz le cache du plan et demandez une estimation plan (de pré-exécution) pour la requête de test :

SELECT
    Result.ProductID,
    Result.Name,
    Result.TotalQty
FROM dbo.F(N'K%') AS Result;

Vous verrez deux plans d'exécution, dont le second est pour l'appel de fonction :

Une limitation du reniflage des paramètres avec des fonctions intégrées dans les plans estimés signifie que la valeur du paramètre est reniflée comme NULL (pas "K %") :

Dans les versions de SQL Server antérieures à 2012, ce plan (optimisé pour un NULL paramètre) est mis en cache pour être réutilisé . C'est dommage, car NULL est peu susceptible d'être une valeur de paramètre représentative, et ce n'était certainement pas la valeur spécifiée dans la requête.

SQL Server 2012 (et versions ultérieures) ne met pas en cache les plans résultant d'une demande de "plan estimé", bien qu'il affiche toujours un plan de fonction optimisé pour un NULL valeur du paramètre au moment de la compilation.

Paramétrage simple et forcé

Une instruction T-SQL ad hoc contenant des valeurs littérales constantes peut être paramétrée par SQL Server, soit parce que la requête se qualifie pour un paramétrage simple, soit parce que l'option de base de données pour le paramétrage forcé est activée (ou qu'un guide de plan est utilisé dans le même effet).

Une instruction paramétrée de cette manière est également sujette au reniflage de paramètres. La requête suivante se qualifie pour un paramétrage simple :

SELECT 
    A.AddressLine1, 
    A.City, 
    A.PostalCode 
FROM Person.Address AS A 
WHERE 
    A.AddressLine1 = N'Heidestieg Straße 8664';

Le plan d'exécution estimé affiche une estimation de 2,5 lignes en fonction de la valeur du paramètre reniflé :

En fait, la requête renvoie 7 lignes (l'estimation de la cardinalité n'est pas parfaite, même lorsque les valeurs sont reniflées) :

À ce stade, vous vous demandez peut-être où se trouve la preuve que cette requête a été paramétrée et que la valeur de paramètre résultante a été reniflée. Exécutez la requête une deuxième fois avec une valeur différente :

SELECT 
    A.AddressLine1, 
    A.City, 
    A.PostalCode 
FROM Person.Address AS A 
WHERE 
    A.AddressLine1 = N'Winter der Böck 8550';

La requête renvoie une ligne :

Le plan d'exécution montre que la deuxième exécution a réutilisé le plan paramétré qui a été compilé à l'aide d'une valeur sniffée :

Le paramétrage et le sniffing sont des activités distinctes

Une instruction ad-hoc peut être paramétrée par SQL Server sans les valeurs des paramètres sont reniflées.

Pour démontrer, nous pouvons utiliser l'indicateur de trace 4136 pour désactiver le reniflage des paramètres pour un lot qui sera paramétré par le serveur :

DBCC FREEPROCCACHE;
DBCC TRACEON (4136);
GO
SELECT
    A.AddressLine1, 
    A.City, 
    A.PostalCode 
FROM Person.Address AS A 
WHERE
    A.AddressLine1 = N'Heidestieg Straße 8664';
GO
SELECT 
    A.AddressLine1, 
    A.City, 
    A.PostalCode 
FROM Person.Address AS A 
WHERE 
    A.AddressLine1 = N'Winter der Böck 8550';
GO
DBCC TRACEOFF (4136);

Le script génère des instructions paramétrées, mais la valeur du paramètre n'est pas reniflée à des fins d'estimation de cardinalité. Pour le voir, nous pouvons inspecter le cache du plan :

WITH XMLNAMESPACES
    (DEFAULT 'http://schemas.microsoft.com/sqlserver/2004/07/showplan')
SELECT
    DECP.cacheobjtype,
    DECP.objtype,
    DECP.usecounts,
    DECP.plan_handle,
    parameterized_plan_handle =
        DEQP.query_plan.value
        (
            '(//StmtSimple)[1]/@ParameterizedPlanHandle',
            'NVARCHAR(100)'
        )
FROM sys.dm_exec_cached_plans AS DECP
CROSS APPLY sys.dm_exec_sql_text(DECP.plan_handle) AS DEST
CROSS APPLY sys.dm_exec_query_plan(DECP.plan_handle) AS DEQP
WHERE 
    DEST.[text] LIKE N'%AddressLine1%'
    AND DEST.[text] NOT LIKE N'%XMLNAMESPACES%';

Les résultats affichent deux entrées de cache pour les requêtes ad hoc, liées au plan de requête paramétré (préparé) par le descripteur de plan paramétré.

Le plan paramétré est utilisé deux fois :

Le plan d'exécution affiche une estimation de cardinalité différente maintenant que la détection des paramètres est désactivée :

Comparez l'estimation de 1,44571 lignes avec l'estimation de 2,5 lignes utilisée lorsque la détection des paramètres était activée.

Lorsque le reniflage est désactivé, l'estimation provient des informations de fréquence moyenne sur AddressLine1 colonne. Un extrait du DBCC SHOW_STATISTICS la sortie pour l'index en question montre comment ce nombre a été calculé :la multiplication du nombre de lignes dans le tableau (19 614) par la densité (7,370826e-5) donne l'estimation de 1,44571 lignes.

Remarque : Il est communément admis que seules les comparaisons d'entiers utilisant un index unique peuvent se qualifier pour un paramétrage simple. J'ai délibérément choisi cet exemple (une comparaison de chaînes utilisant un index non unique) pour réfuter cela.

AVEC RECOMPILE et OPTION (RECOMPILE)

Lorsqu'un problème de sensibilité aux paramètres est rencontré, un conseil courant sur les forums et les sites de questions-réponses consiste à "utiliser la recompilation" (en supposant que les autres options de réglage présentées précédemment ne conviennent pas). Malheureusement, ce conseil est souvent interprété à tort comme signifiant l'ajout de WITH RECOMPILE option à la procédure stockée.

Utilisation de WITH RECOMPILE nous ramène effectivement au comportement de SQL Server 2000, où la procédure stockée entière est recompilé à chaque exécution.

Une meilleure alternative , sur SQL Server 2005 et versions ultérieures, consiste à utiliser l'OPTION (RECOMPILE) indice de requête sur la seule instruction qui souffre du problème de reniflage de paramètres. Cet indicateur de requête entraîne une recompilation de l'instruction problématique seul. Les plans d'exécution des autres instructions de la procédure stockée sont mis en cache et réutilisés normalement.

Utilisation de WITH RECOMPILE signifie également que le plan compilé pour la procédure stockée n'est pas mis en cache. Par conséquent, aucune information de performance n'est conservée dans les DMV telles que sys.dm_exec_query_stats .

L'utilisation de l'indicateur de requête à la place signifie qu'un plan compilé peut être mis en cache et que les informations de performance sont disponibles dans les DMV (bien qu'elles soient limitées à l'exécution la plus récente, pour l'instruction affectée uniquement).

Pour les instances exécutant au moins SQL Server 2008 build 2746 (Service Pack 1 avec mise à jour cumulative 5), en utilisant OPTION (RECOMPILE) a un autre avantage significatif sur WITH RECOMPILE :Uniquement OPTION (RECOMPILE) active l'optimisation de l'intégration des paramètres .

L'optimisation de l'intégration des paramètres

La détection des valeurs de paramètre permet à l'optimiseur d'utiliser la valeur de paramètre pour dériver des estimations de cardinalité. Les deux WITH RECOMPILE et OPTION (RECOMPILE) génèrent des plans de requête avec des estimations calculées à partir des valeurs réelles des paramètres à chaque exécution.

L'optimisation de l'intégration des paramètres pousse ce processus un peu plus loin. Les paramètres de requête sont remplacés avec des valeurs constantes littérales lors de l'analyse de la requête.

L'analyseur est capable de simplifications étonnamment complexes, et l'optimisation ultérieure des requêtes peut encore affiner les choses. Considérez la procédure stockée suivante, qui comporte le WITH RECOMPILE choix :

CREATE PROCEDURE dbo.P
    @NameLike nvarchar(50),
    @Sort tinyint
WITH RECOMPILE
AS
BEGIN
    SELECT TOP (5)
        ProductID,
        Name
    FROM Production.Product
    WHERE
        @NameLike IS NULL
        OR Name LIKE @NameLike
    ORDER BY
        CASE WHEN @Sort = 1 THEN ProductID ELSE NULL END ASC,
        CASE WHEN @Sort = 2 THEN ProductID ELSE NULL END DESC,
        CASE WHEN @Sort = 3 THEN Name ELSE NULL END ASC,
        CASE WHEN @Sort = 4 THEN Name ELSE NULL END DESC;
END;

La procédure est exécutée deux fois, avec les valeurs de paramètres suivantes :

EXECUTE dbo.P
	@NameLike = N'K%',
	@Sort = 1;
GO
EXECUTE dbo.P
	@NameLike = N'[H-R]%',
	@Sort = 4;

Parce que WITH RECOMPILE est utilisé, la procédure est entièrement recompilée à chaque exécution. Les valeurs des paramètres sont sniffées à chaque fois, et utilisé par l'optimiseur pour calculer les estimations de cardinalité.

Le plan pour la première exécution de la procédure est tout à fait correct, estimant 1 ligne :

La deuxième exécution estime 360 lignes, très proches des 366 vues à l'exécution :

Les deux plans utilisent la même stratégie d'exécution générale :analysez toutes les lignes d'un index, en appliquant le WHERE prédicat de clause comme résidu ; calculer le CASE expression utilisée dans le ORDER BY clause; et effectuez un Tri Top N sur le résultat du CASE expression.

OPTION (RECOMPILER)

Recréez maintenant la procédure stockée en utilisant une OPTION (RECOMPILE) indice de requête au lieu de WITH RECOMPILE :

CREATE PROCEDURE dbo.P
    @NameLike nvarchar(50),
    @Sort tinyint
AS
BEGIN
    SELECT TOP (5)
        ProductID,
        Name
    FROM Production.Product
    WHERE
        @NameLike IS NULL
        OR Name LIKE @NameLike
    ORDER BY
        CASE WHEN @Sort = 1 THEN ProductID ELSE NULL END ASC,
        CASE WHEN @Sort = 2 THEN ProductID ELSE NULL END DESC,
        CASE WHEN @Sort = 3 THEN Name ELSE NULL END ASC,
        CASE WHEN @Sort = 4 THEN Name ELSE NULL END DESC
    OPTION (RECOMPILE);
END;

L'exécution de la procédure stockée deux fois avec les mêmes valeurs de paramètre qu'auparavant produit des résultats très différents plans d'exécution.

Il s'agit du premier plan d'exécution (avec des paramètres demandant des noms commençant par "K", classés par ProductID croissant):

L'analyseur intègre les valeurs des paramètres dans le texte de la requête, ce qui donne la forme intermédiaire suivante :

SELECT TOP (5)
    ProductID,
    Name
FROM Production.Product
WHERE
    'K%' IS NULL
    OR Name LIKE 'K%'
ORDER BY
    CASE WHEN 1 = 1 THEN ProductID ELSE NULL END ASC,
    CASE WHEN 1 = 2 THEN ProductID ELSE NULL END DESC,
    CASE WHEN 1 = 3 THEN Name ELSE NULL END ASC,
    CASE WHEN 1 = 4 THEN Name ELSE NULL END DESC;

L'analyseur va ensuite plus loin, supprimant les contradictions et évaluant entièrement le CASE expressions. Cela se traduit par :

SELECT TOP (5)
    ProductID,
    Name
FROM Production.Product
WHERE
    Name LIKE 'K%'
ORDER BY
    ProductID ASC,
    NULL DESC,
    NULL ASC,
    NULL DESC;

Vous obtiendrez un message d'erreur si vous essayez de soumettre cette requête directement à SQL Server, car le classement par une valeur constante n'est pas autorisé. Néanmoins, c'est la forme produite par l'analyseur. Il est autorisé en interne car il résulte de l'application de l'optimisation de l'intégration des paramètres . La requête simplifiée facilite grandement la vie de l'optimiseur de requête :

Le balayage d'index groupé applique le LIKE prédicat comme résidu. Le scalaire de calcul fournit la constante NULL valeurs. Le Haut renvoie les 5 premières lignes dans l'ordre fourni par l'index clusterisé (en évitant un tri). Dans un monde parfait, l'optimiseur de requête supprimerait également le Compute Scalar qui définit les NULLs , car ils ne sont pas utilisés lors de l'exécution de la requête.

La deuxième exécution suit exactement le même processus, aboutissant à un plan de requête (pour les noms commençant par les lettres "H" à "R", triés par Name décroissant) comme ceci :

Ce plan comprend une recherche d'index non clusterisé qui couvre le LIKE plage, un LIKE résiduel prédicat, la constante NULLs comme avant, et un Top (5). L'optimiseur de requête choisit d'effectuer un BACKWARD balayage de plage dans Index Seek pour encore une fois éviter le tri.

Comparez le plan ci-dessus avec celui produit avec WITH RECOMPILE , qui ne peut pas utiliser l'optimisation de l'intégration des paramètres :

Cet exemple de démonstration aurait peut-être été mieux implémenté sous la forme d'une série de IF instructions dans la procédure (une pour chaque combinaison de valeurs de paramètre). Cela pourrait fournir des avantages de plan de requête similaires, sans nécessiter une compilation d'instructions à chaque fois. Dans des scénarios plus complexes, la recompilation au niveau de l'instruction avec l'incorporation de paramètres fournie par OPTION (RECOMPILE) peut être une technique d'optimisation extrêmement utile.

Une restriction d'intégration

Il existe un scénario où l'utilisation de OPTION (RECOMPILE) n'entraînera pas l'application de l'optimisation de l'intégration des paramètres. Si l'instruction est affectée à une variable, les valeurs des paramètres ne sont pas intégrées :

CREATE PROCEDURE dbo.P
    @NameLike nvarchar(50),
    @Sort tinyint
AS
BEGIN
    DECLARE
        @ProductID integer,
        @Name nvarchar(50);
 
    SELECT TOP (1)
        @ProductID = ProductID,
        @Name = Name
    FROM Production.Product
    WHERE
        @NameLike IS NULL
        OR Name LIKE @NameLike
    ORDER BY
        CASE WHEN @Sort = 1 THEN ProductID ELSE NULL END ASC,
        CASE WHEN @Sort = 2 THEN ProductID ELSE NULL END DESC,
        CASE WHEN @Sort = 3 THEN Name ELSE NULL END ASC,
        CASE WHEN @Sort = 4 THEN Name ELSE NULL END DESC
    OPTION (RECOMPILE);
END;

Parce que le SELECT assigne maintenant à une variable, les plans de requête produits sont les mêmes que lorsque WITH RECOMPILE a été utilisé. Les valeurs des paramètres sont toujours reniflées et utilisées par l'optimiseur de requête pour l'estimation de la cardinalité, et OPTION (RECOMPILE) ne compile toujours que la seule instruction, seul l'avantage de l'incorporation de paramètres est perdu.