Ejecutar la consulta desde aquí para extraer los eventos de punto muerto de la sesión de eventos extendidos predeterminada
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';
Tarda unos 20 minutos en completarse en mi máquina. Las estadísticas reportadas son
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 elimino la WHEREcláusula, se completa en menos de un segundo y devuelve 3.782 filas.
Del mismo modo, si agrego OPTION (MAXDOP 1)a la consulta original, eso también acelera las cosas con las estadísticas que ahora muestran muchísimo menos lecturas de lóbulos.
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.
Entonces mi pregunta es
¿Alguien puede explicar lo que está pasando? ¿Por qué el plan original es tan catastróficamente peor y hay alguna forma confiable de evitar el problema?
Adición:
También descubrí que cambiar la consulta para INNER HASH JOINmejorar las cosas hasta cierto punto (pero aún toma más de 3 minutos) ya que los resultados del DMV son tan pequeños que dudo que el tipo de unión en sí sea responsable y supongo que algo más debe haber cambiado. Estadísticas para eso
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.
Después de llenar el búfer de anillo de eventos extendidos ( DATALENGTHde XML4,880,045 bytes y contenía 1,448 eventos) y probar una versión reducida de la consulta original con y sin la MAXDOPsugerencia.
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
Dio los siguientes resultados
+-------------------------------------+------+----------+
| | 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 |
+-------------------------------------+------+----------+
Hay una clara diferencia en las asignaciones tempdb con la más rápida que muestra las 616páginas asignadas y desasignadas. Esta es la misma cantidad de páginas utilizadas cuando el XML también se coloca en una variable.
Para el plan lento, estos recuentos de asignación de páginas están en millones. El sondeo dm_db_task_space_usagemientras se ejecuta la consulta muestra que parece estar constantemente asignando y desasignando páginas tempdbcon entre 1.800 y 3.000 páginas asignadas en cualquier momento.
fuente



WHEREcláusula a la expresión XQuery; la lógica no tiene que ser eliminado para que vaya rápido:TargetData.nodes ('RingBufferTarget[1]/event[@name = "xml_deadlock_report"]'). Dicho esto, no conozco los componentes internos de XML lo suficientemente bien como para responder la pregunta que ha planteado.Respuestas:
La razón de la diferencia de rendimiento radica en cómo se manejan las expresiones escalares en el motor de ejecución. En este caso, la expresión de interés es:
Esta etiqueta de expresión está definida por un operador Compute Scalar (nodo 11 en el plan en serie, nodo 13 en el plan paralelo). Los operadores de Compute Scalar son diferentes de otros operadores (SQL Server 2005 en adelante) en que las expresiones que definen no se evalúan necesariamente en la posición en que aparecen en el plan de ejecución visible; la evaluación puede diferirse hasta que un operador posterior requiera el resultado del cálculo.
En la presente consulta, la
target_datacadena suele ser grande, lo que hace que la conversión de cadena aXMLcostosa. En planes lentos, la cadena deXMLconversión se realiza cada vez que un operador posterior que requiere el resultado delExpr1000rebote.El rebobinado se produce en el lado interno de una unión de bucles anidados cuando cambia un parámetro correlacionado (referencia externa).
Expr1000es una referencia externa para la mayoría de las uniones de bucles anidados en este plan de ejecución. Varios lectores XML hacen referencia a la expresión varias veces, tanto Stream Aggregates como un filtro de inicio. Dependiendo del tamaño de laXML, la cantidad de veces que se convierte la cadenaXMLpuede ser fácilmente en millones.Las pilas de llamadas a continuación muestran ejemplos de la
target_datacadena que se está convirtiendoXML(ConvertStringToXMLForES- donde ES es el servicio de expresión ):Filtro de arranque
Lector XML (TVF Stream internamente)
Agregado de flujo
La conversión de la cadena a
XMLcada uno de estos operadores vuelve a vincular explica la diferencia de rendimiento observada con los planes de bucles anidados. Esto es independientemente de si se usa el paralelismo o no. Sucede que el optimizador elige una unión hash cuandoMAXDOP 1se especifica la sugerencia. SiMAXDOP 1, LOOP JOINse especifica, el rendimiento es deficiente al igual que con el plan paralelo predeterminado (donde el optimizador elige bucles anidados).La cantidad de rendimiento que aumenta con una combinación hash depende de si
Expr1000aparece en el lado de compilación o sonda del operador. La siguiente consulta localiza la expresión en el lado de la sonda:He invertido el orden escrito de las combinaciones de la versión que se muestra en la pregunta, porque las sugerencias de combinación (
INNER HASH JOINarriba) también fuerzan el orden de toda la consulta, como si seFORCE ORDERhubiera especificado. La inversión es necesaria para garantizar queExpr1000aparezca en el lado de la sonda. La parte interesante del plan de ejecución es:Con la expresión definida en el lado de la sonda, el valor se almacena en caché:
La evaluación de
Expr1000todavía se difiere hasta que el primer operador necesita el valor (el filtro de inicio en el seguimiento de la pila anterior) pero el valor calculado se almacena en caché (CValHashCachedSwitch) y los lectores XML y agregados de flujo lo reutilizan para llamadas posteriores. El seguimiento de la pila a continuación muestra un ejemplo del valor almacenado en caché que un lector XML está reutilizando.Cuando el orden de unión se fuerza de tal manera que la definición de
Expr1000ocurre en el lado de construcción de la unión hash, la situación es diferente:Una combinación hash lee su entrada de compilación por completo para construir una tabla hash antes de que comience a buscar coincidencias. Como resultado, tenemos que almacenar todos los valores, no solo el de cada subproceso que se está trabajando desde el lado de la sonda del plan. Por lo tanto, la combinación hash utiliza una
tempdbtabla de trabajo para almacenar losXMLdatos, y cada acceso al resultado de losExpr1000operadores posteriores requiere un viaje costoso paratempdb:A continuación se muestran más detalles de la ruta de acceso lento:
Si se fuerza una combinación de fusión, las filas de entrada se ordenan (una operación de bloqueo, al igual que la entrada de compilación para una combinación hash), lo que da como resultado una disposición similar en la que
tempdbse requiere un acceso lento a través de una tabla de trabajo optimizada para la clasificación debido al tamaño de los datos.Los planes que manipulan elementos de datos grandes pueden ser problemáticos por todo tipo de razones que no son evidentes en el plan de ejecución. Usar una combinación hash (con la expresión en la entrada correcta) no es una buena solución. Se basa en un comportamiento interno no documentado sin garantías de que funcionará de la misma manera la próxima semana, o en una consulta ligeramente diferente.
El mensaje es que la
XMLmanipulación puede ser algo difícil de optimizar hoy. EscribirXMLen una tabla variable o temporal antes de la trituración es una solución mucho más sólida que cualquier cosa que se muestra arriba. Una forma de hacer esto es:Finalmente, solo quiero agregar el bonito gráfico de Martin de los comentarios a continuación:
fuente
@@IEAAXPEA_Kaparecer.Ese es el código de mi artículo publicado originalmente aquí:
http://www.sqlservercentral.com/articles/deadlock/65658/
Si lees los comentarios, encontrarás un par de alternativas que no tienen los problemas de rendimiento que estás experimentando, una que usa una modificación de esa consulta original y la otra que usa una variable para contener el XML antes de procesarlo, lo que funciona mejor. (vea mis comentarios en la página 2) El XML del DMV puede ser lento de procesar, al igual que el análisis del XML del DMF para el destino del archivo, que a menudo se logra mejor leyendo primero los datos en una tabla temporal y luego procesándolos. XML en SQL es lento en comparación con el uso de cosas como .NET o SQLCLR.
fuente
303 msy3249 lob reads. En 2012 también necesitaba agregarand target_name='ring_buffer'a esa versión, ya que parece que ahora hay dos objetivos. Sin embargo, todavía estoy tratando de obtener una imagen mental de lo que está haciendo exactamente en la versión de 20 minutos.