Exécution de la requête à partir d'ici pour extraire les événements d'interblocage de la session d'événements étendus par défaut
SELECT CAST (
REPLACE (
REPLACE (
XEventData.XEvent.value ('(data/value)[1]', 'varchar(max)'),
'<victim-list>', '<deadlock><victim-list>'),
'<process-list>', '</victim-list><process-list>')
AS XML) AS DeadlockGraph
FROM (SELECT CAST (target_data AS XML) AS TargetData
FROM sys.dm_xe_session_targets st
JOIN sys.dm_xe_sessions s ON s.address = st.event_session_address
WHERE [name] = 'system_health') AS Data
CROSS APPLY TargetData.nodes ('//RingBufferTarget/event') AS XEventData (XEvent)
WHERE XEventData.XEvent.value('@name', 'varchar(4000)') = 'xml_deadlock_report';
prend environ 20 minutes à compléter sur ma machine. Les statistiques rapportées sont
Table 'Worktable'. Scan count 0, logical reads 68121, physical reads 0, read-ahead reads 0,
lob logical reads 25674576, lob physical reads 0, lob read-ahead reads 4332386.
SQL Server Execution Times:
CPU time = 1241269 ms, elapsed time = 1244082 ms.
Si je supprime la WHERE
clause, elle se termine en moins d’une seconde et renvoie 3 782 lignes.
De la même manière, si j'ajoute OPTION (MAXDOP 1)
à la requête initiale une accélération du processus, les statistiques affichent désormais un nombre de lectures massivement moins élevé.
Table 'Worktable'. Scan count 0, logical reads 15, physical reads 0, read-ahead reads 0,
lob logical reads 6767, lob physical reads 0, lob read-ahead reads 6076.
SQL Server Execution Times:
CPU time = 639 ms, elapsed time = 693 ms.
Donc ma question est
Quelqu'un peut-il expliquer ce qui se passe? Pourquoi le plan initial est-il si catastrophiquement pire et existe-t-il un moyen fiable d'éviter le problème?
Une addition:
J'ai également constaté que modifier la requête INNER HASH JOIN
améliore quelque peu les choses (mais cela prend toujours plus de 3 minutes) car les résultats DMV sont si petits que je doute que le type de jointure lui-même soit responsable et présume que quelque chose d'autre doit avoir changé. Statistiques pour ça
Table 'Worktable'. Scan count 0, logical reads 30294, physical reads 0, read-ahead reads 0,
lob logical reads 10741863, lob physical reads 0, lob read-ahead reads 4361042.
SQL Server Execution Times:
CPU time = 200914 ms, elapsed time = 203614 ms.
Après avoir rempli le tampon DATALENGTH
d’ XML
annonce des événements étendus (4 880 045 octets, contenant 1448 événements), et testé une version réduite de la requête initiale avec et sans MAXDOP
indication.
SELECT COUNT(*)
FROM (SELECT CAST (target_data AS XML) AS TargetData
FROM sys.dm_xe_session_targets st
JOIN sys.dm_xe_sessions s
ON s.address = st.event_session_address
WHERE [name] = 'system_health') AS Data
CROSS APPLY TargetData.nodes ('//RingBufferTarget/event') AS XEventData (XEvent)
WHERE XEventData.XEvent.value('@name', 'varchar(4000)') = 'xml_deadlock_report'
SELECT*
FROM sys.dm_db_task_space_usage
WHERE session_id = @@SPID
A donné les résultats suivants
+-------------------------------------+------+----------+
| | Fast | Slow |
+-------------------------------------+------+----------+
| internal_objects_alloc_page_count | 616 | 1761272 |
| internal_objects_dealloc_page_count | 616 | 1761272 |
| elapsed time (ms) | 428 | 398481 |
| lob logical reads | 8390 | 12784196 |
+-------------------------------------+------+----------+
Il existe une nette différence dans les allocations de tempdb, la plus rapide montrant que les 616
pages ont été allouées et désallouées. Cela correspond au même nombre de pages utilisées lorsque le XML est également placé dans une variable.
Pour le plan lent, ces comptes d'allocation de pages se chiffrent en millions. Le fait de scruter dm_db_task_space_usage
pendant que la requête est en cours montre qu’il semble allouer et désallouer en permanence des pages, tempdb
avec entre 1 800 et 3 000 pages allouées à la fois.
la source
WHERE
clause dans l'expression XQuery. la logique ne doit pas être supprimé car pour aller vite:TargetData.nodes ('RingBufferTarget[1]/event[@name = "xml_deadlock_report"]')
. Cela dit, je ne connais pas suffisamment bien les composants internes XML pour répondre à la question que vous avez posée.Réponses:
La différence de performances tient à la manière dont les expressions scalaires sont gérées dans le moteur d’exécution. Dans ce cas, la manifestation d'intérêt est:
Cette étiquette d'expression est définie par un opérateur Compute Scalar (noeud 11 dans le plan série, noeud 13 dans le plan parallèle). Les opérateurs de calcul Scalar sont différents des autres opérateurs (SQL Server 2005 et ultérieur) en ce que les expressions qu'ils définissent ne sont pas nécessairement évalués à la position où ils apparaissent. dans le plan d'exécution visible; l'évaluation peut être différée jusqu'à ce que le résultat du calcul soit requis par un opérateur ultérieur.
Dans la requête actuelle, la
target_data
chaîne est généralement longue, ce qui rend la conversion de chaîne enXML
chère. Dans les plans lents, laXML
conversion en chaîne est effectuée chaque fois qu'un opérateur ultérieur nécessitant le résultat deExpr1000
son rebond.La reliure a lieu du côté intérieur des boucles imbriquées qui se rejoignent lorsqu'un paramètre corrélé (référence externe) change.
Expr1000
est une référence externe pour la plupart des jointures de boucles imbriquées dans ce plan d'exécution. L'expression est référencée plusieurs fois par plusieurs lecteurs XML, à la fois Agrégats de flux et par un filtre de démarrage. En fonction de la taille de laXML
, le nombre de fois que la chaîne est convertie enXML
peut facilement se chiffrer en millions.Les piles d’appel ci-dessous montrent des exemples de
target_data
chaîne convertie enXML
(ConvertStringToXMLForES
- où ES est le service d’expression ):Filtre de démarrage
Lecteur XML (flux TVF en interne)
Agrégat de flux
La conversion de la chaîne à
XML
chaque fois que l'un de ces opérateurs effectue un rapprochement explique la différence de performances observée avec les plans de boucles imbriquées. Ceci indépendamment du fait que le parallélisme soit utilisé ou non. Il se trouve que l'optimiseur choisit une jointure de hachage lorsque l'MAXDOP 1
indicateur est spécifié. SiMAXDOP 1, LOOP JOIN
est spécifié, les performances sont médiocres, tout comme avec le plan parallèle par défaut (où l'optimiseur choisit des boucles imbriquées).L'augmentation des performances avec une jointure de hachage dépend
Expr1000
de l'affichage ou non de la construction ou de la sonde de l'opérateur. La requête suivante localise l'expression du côté de la sonde:J'ai inversé l'ordre écrit des jointures par rapport à la version indiquée dans la question, car les indications de jointure (
INNER HASH JOIN
ci-dessus) forcent également l'ordre de la requête entière, comme si celaFORCE ORDER
avait été spécifié. L’inversion est nécessaire pour que l’Expr1000
apparence apparaisse du côté de la sonde. La partie intéressante du plan d'exécution est la suivante:Avec l'expression définie du côté de la sonde, la valeur est mise en cache:
L'évaluation de
Expr1000
est toujours différée jusqu'à ce que le premier opérateur ait besoin de la valeur (le filtre de démarrage dans la trace de pile ci-dessus) mais la valeur calculée est mise en cache (CValHashCachedSwitch
) et réutilisée pour les appels ultérieurs par les lecteurs XML et les agrégats de flux. La trace de pile ci-dessous montre un exemple de la valeur en cache réutilisée par un lecteur XML.Lorsque l'ordre de jointure est forcé de telle sorte que la définition de
Expr1000
se produise du côté de la construction de la jointure de hachage, la situation est différente:Une jointure de hachage lit complètement son entrée de construction pour construire une table de hachage avant de commencer à rechercher des correspondances. En conséquence, nous devons stocker toutes les valeurs, pas uniquement celle par thread traitée du côté de la sonde du plan. La jointure de hachage utilise donc une
tempdb
table de travail pour stocker lesXML
données, et chaque accès au résultat généréExpr1000
par des opérateurs ultérieurs nécessite un trajet coûteux pourtempdb
:Ce qui suit montre plus de détails sur le chemin d'accès lent:
Si une jointure par fusion est forcée, les lignes d'entrée sont triées (une opération de blocage, tout comme l'entrée de construction dans une jointure de hachage), ce qui entraîne un arrangement similaire dans lequel un accès lent via une
tempdb
table de travail à tri optimisé est requis en raison de la taille des données.Les plans qui manipulent des éléments de données volumineux peuvent poser problème pour toutes sortes de raisons qui ne ressortent pas du plan d'exécution. L'utilisation d'une jointure de hachage (avec l'expression sur la bonne entrée) n'est pas une bonne solution. Il repose sur un comportement interne non documenté, sans aucune garantie qu'il fonctionnera de la même manière la semaine prochaine, ou sur une requête légèrement différente.
Le message est que la
XML
manipulation peut être difficile à optimiser aujourd'hui. Écrire laXML
dans une table de variables ou temporaire avant le déchiquetage est une solution de contournement beaucoup plus solide que tout ce qui est présenté ci-dessus. Une façon de faire est:Enfin, je veux juste ajouter le très joli graphique de Martin tiré des commentaires ci-dessous:
la source
@@IEAAXPEA_K
qu'apparition.C'est le code de mon article initialement publié ici:
http://www.sqlservercentral.com/articles/deadlock/65658/
Si vous lisez les commentaires, vous trouverez plusieurs solutions qui ne présentent pas les problèmes de performances que vous rencontrez, l’une utilisant une modification de la requête initiale et l’autre utilisant une variable permettant de conserver le code XML avant de le traiter, ce qui fonctionne. mieux. (Voir mes commentaires à la page 2) Le traitement de XML à partir de fichiers DMV peut être lent, tout comme l'analyse de XML à partir de DMF pour le fichier cible, ce qui est souvent mieux accompli en lisant d'abord les données dans une table temporaire, puis en les traitant. XML en SQL est lent par rapport à l'utilisation de choses comme .NET ou SQLCLR.
la source
303 ms
et3249 lob reads
. En 2012, je devais également ajouterand target_name='ring_buffer'
à cette version, qui ressemble maintenant à deux cibles. J'essaie toujours d'obtenir une image mentale de ce que cela fait exactement dans la version 20 minutes.