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

Capturer les avertissements du plan d'exécution à l'aide d'événements étendus

Nous enseignons IEPTO2 à Dublin cette semaine (et si l'Irlande n'est pas sur votre liste d'endroits à voir dans cette vie, vous devez l'ajouter… c'est fantastique ici) et aujourd'hui j'ai terminé le module d'analyse du plan de requête. Une chose que je couvre est des choses intéressantes que vous pouvez trouver dans le plan de requête, par exemple :

  • NoJoinPredicate (2005 et supérieur)
  • ColumnsWithNoStatistics (2005 et supérieur)
  • Index sans correspondance (2008 et versions ultérieures)
  • PlanAffectingConvert (2012 et versions ultérieures)

Ces attributs sont bons à rechercher lorsque vous examinez un plan unique ou un ensemble de plans, pendant que vous vous ajustez. Mais si vous voulez être un peu plus proactif, vous pouvez commencer à exploiter le cache du plan et à les rechercher là-bas. Bien sûr, cela nécessite d'écrire du XQuery, puisque les plans sont XML (pour plus de détails sur le schéma showplan, consultez :http://schemas.microsoft.com/sqlserver/2004/07/showplan/). Je n'aime pas XML, mais pas faute d'essayer, et quand l'un des participants a demandé si vous pouviez capturer des requêtes qui avaient l'attribut NoJoinPredicate via des événements étendus, j'ai pensé :« Quelle bonne idée, je vais devoir vérifier ."

Effectivement, il y a un événement pour ça. Il y a un événement pour les quatre de ceux que j'ai énumérés ci-dessus :

  • missing_join_predicate
  • missing_column_statistics
  • unmatched_filtered_indexes
  • plan_affecting_convert

Joli. Les configurer dans une session d'événements étendus est assez simple. Dans ce cas, je recommanderais d'utiliser la cible event_file, car vous allez probablement démarrer la session d'événements et la laisser s'exécuter un peu avant de revenir en arrière et de revoir la sortie. J'ai inclus quelques actions, dans l'espoir que ces événements ne soient pas déclenchés que souvent, donc nous n'ajoutons pas trop de frais généraux ici. J'ai inclus sql_text même si ce n'est pas une action sur laquelle vous devriez vraiment compter. Jonathan en a déjà parlé, mais sql_text ne fait que vous donner le tampon d'entrée, vous n'obtiendrez donc peut-être pas l'histoire complète de la requête. Pour cette raison, j'ai également inclus plan_handle. La mise en garde étant que, selon le moment où vous allez chercher le plan, il se peut qu'il ne soit plus dans le cache du plan.

-- Remove event session if it exists
IF EXISTS (SELECT 1 FROM [sys].[server_event_sessions]
WHERE [name] = 'InterestingPlanEvents')
BEGIN
  DROP EVENT SESSION [InterestingPlanEvents] ON SERVER
END
GO
 
-- Define event session
CREATE EVENT SESSION [InterestingPlanEvents]
ON SERVER
ADD EVENT sqlserver.missing_column_statistics
(
  ACTION(sqlserver.database_id,sqlserver.plan_handle,sqlserver.sql_text)
  WHERE ([package0].[equal_boolean]([sqlserver].[is_system],(0))
    AND [sqlserver].[database_id]>(4))
),
ADD EVENT sqlserver.missing_join_predicate
(
  ACTION(sqlserver.database_id,sqlserver.plan_handle,sqlserver.sql_text)
  WHERE ([sqlserver].[is_system]=(0) AND [sqlserver].[database_id]>(4))
),
ADD EVENT sqlserver.plan_affecting_convert
(
  ACTION(sqlserver.database_id,sqlserver.plan_handle,sqlserver.sql_text)
  WHERE ([package0].[equal_boolean]([sqlserver].[is_system],(0))
    AND [sqlserver].[database_id]>(4))
),
ADD EVENT sqlserver.unmatched_filtered_indexes
(
  ACTION(sqlserver.plan_handle,sqlserver.sql_text)
  WHERE ([package0].[equal_boolean]([sqlserver].[is_system],(0))
    AND [sqlserver].[database_id]>(4))
)
ADD TARGET package0.event_file
(
  SET filename=N'C:\temp\InterestingPlanEvents' /* change location if appropriate */
)
WITH (MAX_MEMORY=4096 KB,EVENT_RETENTION_MODE=ALLOW_SINGLE_EVENT_LOSS,
MAX_DISPATCH_LATENCY=5 SECONDS,MAX_EVENT_SIZE=0 KB,MEMORY_PARTITION_MODE=NONE,
TRACK_CAUSALITY=ON,STARTUP_STATE=OFF)
GO
 
-- Start the event session
ALTER EVENT SESSION [InterestingPlanEvents] ON SERVER STATE=START;
GO

Une fois que la session d'événements est opérationnelle, nous pouvons générer ces événements avec l'exemple de code ci-dessous. Notez que ce code suppose une nouvelle installation d'AdventureWorks2014. Si vous n'en avez pas, vous ne verrez peut-être pas l'événement missing_column_statistics se déclencher si vous êtes interrogé sur la colonne [HireDate] dans [HumanResources].[Employee].

-- These queries assume a FRESH restore of AdventureWorks2014
ALTER DATABASE [AdventureWorks2014] SET AUTO_CREATE_STATISTICS OFF;
GO
 
USE [AdventureWorks2014];
GO
 
CREATE INDEX [NCI_SalesOrderHeader] ON [Sales].[SalesOrderHeader] (
[PurchaseOrderNumber], [CustomerID], [TotalDue], [DueDate]
)
WHERE [SubTotal] > 10000.00;
GO
 
/*
No join predicate
NOTE: We clear procedure here because the event ONLY fires for the *initial* compilation
*/
DBCC FREEPROCCACHE; /* Not for production use */
 
SELECT [h].[SalesOrderID], [d].[SalesOrderDetailID], [h].[CustomerID]
FROM [Sales].[SalesOrderDetail] [d],
[Sales].[SalesOrderHeader] [h]
WHERE [d].[ProductID] = 897;
GO
 
-- Columns with no statistics
SELECT [BusinessEntityID], [NationalIDNumber], [JobTitle], [HireDate], [ModifiedDate]
FROM [HumanResources].[Employee]
WHERE [HireDate] >= '2013-01-01';
GO
 
-- Unmatched Index
DECLARE @Total MONEY = 10000.00;
 
SELECT [PurchaseOrderNumber], [CustomerID], [TotalDue], [DueDate]
FROM [Sales].[SalesOrderHeader]
WHERE [SubTotal] > @Total;
GO
 
-- Plan Affecting Convert
SELECT [BusinessEntityID], [NationalIDNumber], [JobTitle], [HireDate], [ModifiedDate]
FROM [HumanResources].[Employee]
WHERE [NationalIDNumber] = 345106466;
GO
 
ALTER EVENT SESSION [InterestingPlanEvents]
ON SERVER
STATE=STOP;
GO
 
DROP EVENT SESSION [InterestingPlanEvents]
ON SERVER;
GO

REMARQUE :APRÈS avoir terminé d'extraire les plans du cache, vous pouvez exécuter l'instruction ALTER pour activer l'option de création automatique de statistiques. Faire cela à ce stade effacera le cache du plan et vous devrez tout recommencer avec vos tests. (Et attendez également d'avoir terminé pour supprimer l'index.)

ALTER DATABASE [AdventureWorks2014] SET AUTO_CREATE_STATISTICS ON;
GO
 
DROP INDEX [NCI_SalesOrderHeader] ON [Sales].[SalesOrderHeader];
GO

Puisque j'ai arrêté la session d'événement, j'ouvre le fichier de sortie dans SSMS pour voir ce que nous avons capturé :

Sortie des événements étendus

Pour notre première requête avec un prédicat de jointure manquant, nous avons un événement qui s'affiche et je peux voir le texte de la requête dans le champ sql_text. Cependant, ce que je veux vraiment, c'est aussi regarder le plan, afin que je puisse prendre le plan_handle et vérifier sys.dm_exec_query_plan :

SELECT query_plan FROM sys.dm_exec_query_plan
(0x06000700E2200333405DD12C0000000001000000000000000000000000000000000000000000000000000000);

Et l'ouvrir dans SQL Sentry Plan Explorer :

Prédicat de jointure manquant

Le plan a un indicateur visuel du prédicat de jointure manquant dans la boucle imbriquée (le X rouge), et si je le survole, je vois l'avertissement (et c'est dans le XML du plan). Excellent… Je peux maintenant parler à mes développeurs de la réécriture de cette requête.

L'événement suivant concerne une statistique de colonne manquante. J'ai complètement forcé cette situation en désactivant AUTO_CREATE_STATISTICS pour la base de données AdventureWorks2014. Je ne le recommande en aucune façon, forme ou forme. Cette option est activée par défaut et je recommande de toujours la laisser activée. Cependant, le désactiver est le moyen le plus simple de générer cet événement. J'ai à nouveau la requête dans le champ sql_text, mais j'utiliserai à nouveau le plan_handle pour extraire le plan :

SELECT query_plan FROM sys.dm_exec_query_plan
(0x060007004448323810921C360000000001000000000000000000000000000000000000000000000000000000);

Statistique manquante

Et nous avons à nouveau un repère visuel (le triangle jaune avec le point d'exclamation) pour indiquer qu'il y a un problème avec le plan, et encore une fois c'est dans le XML. À partir de là, je vérifierais d'abord si AUTO_CREATE_STATISTICS est désactivé, et si ce n'est pas le cas, je commencerais à exécuter la requête dans Management Studio pour voir si je peux recréer l'avertissement (et contraindre les statistiques à créer).

Maintenant, les événements restants sont un peu plus intéressants.

Vous remarquerez que nous avons trois événements unmatched_filtered_indexes. Je n'ai pas encore déterminé pourquoi, mais j'y travaille et je posterai dans les commentaires si / quand je le ferai trier. Pour l'instant, il suffit que j'ai l'événement, et dans l'événement, nous pouvons également voir les informations sur l'objet afin que je connaisse l'index en question :

Index NCI_SalesOrderHeader référencé par un événement d'index manquant

Et je peux à nouveau prendre le plan_handle pour trouver le plan de requête :

Index sans correspondance

Cette fois, je vois l'avertissement dans l'opérateur SELECT, donc je sais qu'il y a quelque chose que je dois approfondir. Dans ce cas, vous avez des options pour que l'optimiseur utilise l'index filtré lorsque vous utilisez des paramètres, et je vous recommande de consulter le message d'Aaron pour plus d'informations sur l'utilisation des index filtrés.

Enfin, nous avons neuf événements pour plan_affecting_convert. Que diable? Je suis toujours en train de comprendre celui-ci, mais j'ai utilisé l'option Suivre la causalité pour ma session d'événements (lors des tests) pour confirmer que tous les événements font partie de la même tâche (ils le sont). Si vous regardez l'élément d'expression dans la sortie, vous voyez qu'il change légèrement (comme compile_time), et cela apparaît lorsque vous regardez les détails de l'avertissement dans l'explorateur de plans de SQL Sentry (voir la deuxième capture d'écran ci-dessous). Dans la sortie de l'événement, l'élément d'expression does dites-nous quelle colonne est impliquée, ce qui est un début mais pas assez d'informations, donc encore une fois nous devons aller chercher le plan :

SELECT query_plan FROM sys.dm_exec_query_plan
(0x0600070023747010E09E1C360000000001000000000000000000000000000000000000000000000000000000);

Plan affectant la conversion

Détail de la conversion du plan

Nous retrouvons notre ami, le triangle jaune, dans l'opérateur SELECT, et dans le XML, nous pouvons trouver l'attribut PlanAffectingConvert. Cet attribut a été ajouté dans le schéma showplan SQL Server 2012, donc si vous exécutez une version antérieure, vous ne le verrez pas dans le plan. La résolution de cet avertissement peut nécessiter un peu plus de travail - vous devez comprendre où vous rencontrez une incompatibilité de type de données et pourquoi, puis commencer à modifier le code ou le schéma... les deux peuvent rencontrer une résistance. Jonathan a un article qui traite plus en détail de la conversion implicite, ce qui est un bon point de départ si vous n'avez jamais travaillé sur des problèmes de conversion auparavant.

Résumé

La bibliothèque d'événements Extended Events continue de croître, et une chose à considérer lors du dépannage dans SQL Server est de savoir si vous pouvez obtenir les informations que vous recherchez d'une autre manière. Peut-être parce que c'est plus facile (je préfère certainement XE à XML !), Ou parce que c'est plus efficace, ou qu'il vous donne plus de détails. Que vous recherchiez de manière proactive des problèmes de requête dans votre environnement ou que vous réagissiez à un problème signalé par quelqu'un mais que vous ne parveniez pas à le trouver, les événements étendus sont une option viable à envisager, en particulier lorsque de nouvelles fonctionnalités sont ajoutées à SQL Server.