Plans d'exécution
Il est plus compliqué que vous ne le pensez d'après les informations fournies dans les plans d'exécution si une instruction SQL utilise un paramétrage simple . Il n'est pas surprenant que même les utilisateurs SQL Server très expérimentés aient tendance à se tromper, étant donné les informations contradictoires qui nous sont souvent fournies.
Examinons quelques exemples utilisant la base de données Stack Overflow 2010 sur SQL Server 2019 CU 14, avec une compatibilité de base de données définie sur 150.
Pour commencer, nous aurons besoin d'un nouvel index non cluster :
CREATE INDEX [IX dbo.Users Reputation (DisplayName)] ON dbo.Users (Reputation) INCLUDE (DisplayName);
1. Paramétrage simple appliqué
Ce premier exemple de requête utilise un paramétrage simple :
SELECT U.DisplayName FROM dbo.Users AS U WHERE U.Reputation = 999;
L'estimation Le plan (de pré-exécution) comporte les éléments suivants liés au paramétrage :
Propriétés de paramétrage du plan estimé
Remarquez le @1
Le paramètre est introduit partout sauf dans le texte de la requête affiché en haut.
Le réel le plan (post-exécution) a :
Propriétés de paramétrage du plan réel
Notez que la fenêtre des propriétés a maintenant perdu le ParameterizedText
élément, tout en obtenant des informations sur la valeur d'exécution du paramètre. Le texte de requête paramétré est maintenant affiché en haut de la fenêtre avec '@1
' au lieu de '999'.
2. Paramétrage simple non appliqué
Ce deuxième exemple ne fait pas utiliser un paramétrage simple :
-- Projecting an extra column SELECT U.DisplayName, U.CreationDate -- NEW FROM dbo.Users AS U WHERE U.Reputation = 999;
L'estimation le plan montre :
Plan estimé non paramétré
Cette fois, le paramètre @1
est absent de la recherche d'index info-bulle, mais le texte paramétré et les autres éléments de la liste de paramètres sont les mêmes qu'avant.
Regardons le réel plan d'exécution :
Plan réel non paramétré
Les résultats sont les mêmes que le précédent réel paramétré plan, sauf maintenant le Index Seek l'info-bulle affiche la valeur non paramétrée '999'. Le texte de la requête affiché en haut utilise le @1
marqueur de paramètre. La fenêtre des propriétés utilise également @1
et affiche la valeur d'exécution du paramètre.
La requête n'est pas une instruction paramétrée malgré toutes les preuves du contraire.
3. Échec du paramétrage
Mon troisième exemple n'est également pas paramétré par le serveur :
-- LOWER function used SELECT U.DisplayName, LOWER(U.DisplayName) FROM dbo.Users AS U WHERE U.Reputation = 999;
L'estimation plan est :
Échec du paramétrage du plan estimé
Il n'y a aucune mention d'un @1
paramètre n'importe où maintenant, et la Liste des paramètres section de la fenêtre des propriétés est manquante.
Le réel le plan d'exécution est le même, donc je ne prendrai pas la peine de le montrer.
4. Plan Paramétré Parallèle
Je veux vous montrer un autre exemple utilisant le parallélisme dans le plan d'exécution. Le faible coût estimé de mes requêtes de test signifie que nous devons abaisser le seuil de coût pour le parallélisme à 1 :
EXECUTE sys.sp_configure @configname = 'cost threshold for parallelism', @configvalue = 1; RECONFIGURE;
L'exemple est un peu plus complexe cette fois :
SELECT U.DisplayName FROM dbo.Users AS U WHERE U.Reputation >= 5 AND U.DisplayName > N'ZZZ' ORDER BY U.Reputation DESC;
L'estimation plan d'exécution est :
Plan paramétré parallèle estimé
Le texte de la requête en haut reste non paramétré alors que tout le reste l'est. Il y a maintenant deux marqueurs de paramètre, @1
et @2
, car le paramétrage simple trouvé deux valeurs littérales appropriées.
Le réel plan d'exécution suit le schéma de l'exemple 1 :
Plan paramétré parallèle réel
Le texte de la requête en haut est maintenant paramétré et la fenêtre des propriétés contient les valeurs des paramètres d'exécution. Ce plan parallèle (avec a Trier opérateur) est définitivement paramétré par le serveur en utilisant le paramétrage simple .
Méthodes fiables
Il y a des raisons à tous les comportements montrés jusqu'à présent, et quelques autres en plus. J'essaierai d'expliquer bon nombre d'entre eux dans la prochaine partie de cette série lorsque je couvrirai la compilation de plans.
En attendant, la situation avec showplan en général, et SSMS en particulier, est loin d'être idéale. C'est déroutant pour les personnes qui ont travaillé avec SQL Server pendant toute leur carrière. À quels marqueurs de paramètres faites-vous confiance et lesquels ignorez-vous ?
Il existe plusieurs méthodes fiables pour déterminer si un paramétrage simple a été appliqué avec succès ou non à une instruction particulière.
Magasin de requêtes
Je vais commencer par l'un des plus pratiques, le magasin de requêtes. Malheureusement, ce n'est pas toujours aussi simple que vous pourriez l'imaginer.
Vous devez activer la fonctionnalité de magasin de requêtes pour le contexte de la base de données où l'instruction est exécutée et le OPERATION_MODE
doit être défini sur READ_WRITE
, permettant au magasin de requêtes de collecter activement des données.
Une fois ces conditions remplies, la sortie de showplan post-exécution contient des attributs supplémentaires, y compris le StatementParameterizationType . Comme son nom l'indique, celui-ci contient un code décrivant le type de paramétrage utilisé pour l'instruction.
Il est visible dans la fenêtre des propriétés SSMS lorsque le nœud racine d'un plan est sélectionné :
StatementParameterizationType
Les valeurs sont documentées dans sys.query_store_query
:
- 0 – Aucun
- 1 – Utilisateur (paramétrage explicite)
- 2 – Paramétrage simple
- 3 – Paramétrage forcé
Cet attribut bénéfique apparaît uniquement dans SSMS lorsqu'un élément réel plan est demandé et manquant lorsqu'une estimation plan est sélectionné. Il est important de se rappeler que le plan doit être mis en cache . Demander une estimation plan de SSMS ne met pas en cache le plan produit (depuis SQL Server 2012).
Une fois le plan mis en cache, le StatementParameterizationType apparaît aux endroits habituels, y compris via sys.dm_exec_query_plan
.
Vous pouvez également faire confiance aux autres endroits où le type de paramétrage est enregistré dans le magasin de requêtes, comme le query_parameterization_type_desc
colonne dans sys.query_store_query
.
Une mise en garde importante. Lorsque la requête stocke OPERATION_MODE
est défini sur READ_ONLY
, le StatementParameterizationType l'attribut est toujours renseigné dans SSMS réel plans, mais c'est toujours zéro — donnant une fausse impression que l'instruction n'a pas été paramétrée alors qu'elle aurait bien pu l'être.
Si vous êtes satisfait de l'activation du magasin de requêtes, que vous êtes sûr qu'il est en lecture-écriture et que vous ne regardez que les plans de post-exécution dans SSMS, cela fonctionnera pour vous.
Prédicats du plan standard
Le texte de la requête affiché en haut de la fenêtre graphique du plan de présentation dans SSMS n'est pas fiable, comme l'ont montré les exemples. Vous ne pouvez pas non plus vous fier à la ParameterList affiché dans les Propriétés fenêtre lorsque le nœud racine du plan est sélectionné. Le texte paramétré attribut affiché pour estimé seuls les plans ne sont pas non plus concluants.
Vous pouvez cependant vous fier aux propriétés associées aux opérateurs de plan individuels. Les exemples donnés montrent qu'ils sont présents dans les info-bulles lors du survol d'un opérateur.
Un prédicat contenant un marqueur de paramètre comme @1
ou @2
indique un plan paramétré. Les opérateurs les plus susceptibles de contenir un paramètre sont Index Scan , Recherche d'index , et Filtrer .
Prédicats avec marqueurs de paramètres
Si la numérotation commence par @1
, il utilise un paramétrage simple . Le paramétrage forcé commence par @0
. Je dois mentionner que le schéma de numérotation documenté ici est susceptible d'être modifié à tout moment :
Changer l'avertissement
Néanmoins, c'est la méthode que j'utilise le plus souvent pour déterminer si un plan était soumis à un paramétrage côté serveur. Il est généralement rapide et facile de vérifier visuellement un plan pour les prédicats contenant des marqueurs de paramètres. Cette méthode fonctionne également pour les deux types de plans, estimés et réel .
Objets de gestion dynamique
Il existe plusieurs façons d'interroger le cache de plan et les DMO associés pour déterminer si une instruction a été paramétrée. Naturellement, ces requêtes ne fonctionnent que sur les plans en cache, de sorte que l'instruction doit avoir été exécutée jusqu'à la fin, mise en cache et non supprimée par la suite pour quelque raison que ce soit.
L'approche la plus directe consiste à rechercher un Adhoc planifier en utilisant une correspondance textuelle SQL exacte avec la déclaration d'intérêt. Le ad hoc le plan sera un shell contenant un ParameterizedPlanHandle si l'instruction est paramétrée par le serveur. La poignée du plan est ensuite utilisée pour localiser le Préparé plan. Un ad hoc plan n'existera pas si l'optimisation pour les charges de travail ad hoc est activée et que l'instruction en question n'a été exécutée qu'une seule fois.
Ce type de requête finit souvent par déchiqueter une quantité importante de XML et par analyser l'intégralité du cache du plan au moins une fois. Il est également facile de se tromper de code, notamment parce que les plans en cache couvrent un lot entier. Un lot peut contenir plusieurs instructions, chacune pouvant ou non être paramétrée. Tous les DMO ne fonctionnent pas avec la même granularité (lot ou instruction), ce qui facilite le déblocage.
Un moyen efficace de répertorier les déclarations d'intérêt, ainsi que les fragments de plan pour ces déclarations individuelles uniquement, est illustré ci-dessous :
SELECT StatementText = SUBSTRING(T.[text], 1 + (QS.statement_start_offset / 2), 1 + ((QS.statement_end_offset - QS.statement_start_offset) / 2)), IsParameterized = IIF(T.[text] LIKE N'(%', 'Yes', 'No'), query_plan = TRY_CONVERT(xml, P.query_plan) FROM sys.dm_exec_query_stats AS QS CROSS APPLY sys.dm_exec_sql_text (QS.[sql_handle]) AS T CROSS APPLY sys.dm_exec_text_query_plan ( QS.plan_handle, QS.statement_start_offset, QS.statement_end_offset) AS P WHERE -- Statements of interest T.[text] LIKE N'%DisplayName%Users%' -- Exclude queries like this one AND T.[text] NOT LIKE N'%sys.dm%' ORDER BY QS.last_execution_time ASC, QS.statement_start_offset ASC;
Pour illustrer, exécutons un seul lot contenant les quatre exemples précédents :
ALTER DATABASE SCOPED CONFIGURATION CLEAR PROCEDURE_CACHE; GO -- Example 1 SELECT U.DisplayName FROM dbo.Users AS U WHERE U.Reputation = 999; -- Example 2 SELECT U.DisplayName, U.CreationDate FROM dbo.Users AS U WHERE U.Reputation = 999; -- Example 3 SELECT U.DisplayName, LOWER(U.DisplayName) FROM dbo.Users AS U WHERE U.Reputation = 999; -- Example 4 SELECT U.DisplayName FROM dbo.Users AS U WHERE U.Reputation >= 5 AND U.DisplayName > N'ZZZ' ORDER BY U.Reputation DESC; GO
Le résultat de la requête DMO est :
Sortie de la requête DMO
Cela confirme que seuls les exemples 1 et 4 ont été paramétrés avec succès.
Compteurs de performances
Il est possible d'utiliser les compteurs de performances SQL Statistics pour obtenir un aperçu détaillé de l'activité de paramétrage pour les deux estimations et réel des plans. Les compteurs utilisés ne sont pas définis par session, vous devrez donc utiliser une instance de test sans autre activité simultanée pour obtenir des résultats précis.
Je vais compléter les informations du compteur de paramétrage avec les données du sys.dm_exec_query_optimizer_info
DMO fournira également des statistiques sur les plans triviaux.
Une certaine prudence est nécessaire pour empêcher les instructions lisant les informations de compteur de modifier ces compteurs eux-mêmes. Je vais résoudre ce problème en créant quelques procédures stockées temporaires :
CREATE PROCEDURE #TrivialPlans AS SET NOCOUNT ON; SELECT OI.[counter], OI.occurrence FROM sys.dm_exec_query_optimizer_info AS OI WHERE OI.[counter] = N'trivial plan'; GO CREATE PROCEDURE #PerfCounters AS SET NOCOUNT ON; SELECT PC.[object_name], PC.counter_name, PC.cntr_value FROM sys.dm_os_performance_counters AS PC WHERE PC.counter_name LIKE N'%Param%';
Le script pour tester une instruction particulière ressemble alors à ceci :
ALTER DATABASE SCOPED CONFIGURATION CLEAR PROCEDURE_CACHE; GO EXECUTE #PerfCounters; EXECUTE #TrivialPlans; GO SET SHOWPLAN_XML ON; GO -- The statement(s) under test: -- Example 3 SELECT U.DisplayName, LOWER(U.DisplayName) FROM dbo.Users AS U WHERE U.Reputation = 999; GO SET SHOWPLAN_XML OFF; GO EXECUTE #TrivialPlans; EXECUTE #PerfCounters;
Commentez le SHOWPLAN_XML
effectue des lots pour exécuter l'instruction cible (s) et obtenir réel des plans. Laissez-les en place pendant estimation plans d'exécution.
Exécuter le tout tel qu'il est écrit donne les résultats suivants :
Résultats des tests de compteur de performances
J'ai souligné ci-dessus où les valeurs ont changé lors du test de l'exemple 3.
L'augmentation du compteur "plan trivial" de 1050 à 1051 montre qu'un plan trivial a été trouvé pour l'instruction de test.
Les compteurs de paramétrage simple ont augmenté de 1 pour les tentatives et les échecs, indiquant que SQL Server a tenté de paramétrer l'instruction, mais a échoué.
Fin de la partie 3
Dans la prochaine partie de cette série, j'expliquerai les choses curieuses que nous avons vues en décrivant comment le paramétrage simple et plans triviaux interagir avec le processus de compilation.
Si vous avez modifié votre seuil de coût pour le parallélisme pour lancer les exemples, pensez à le réinitialiser (le mien était à 50) :
EXECUTE sys.sp_configure @configname = 'cost threshold for parallelism', @configvalue = 50; RECONFIGURE;