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 WHERE
clá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 JOIN
mejorar 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 ( DATALENGTH
de XML
4,880,045 bytes y contenía 1,448 eventos) y probar una versión reducida de la consulta original con y sin la MAXDOP
sugerencia.
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 616
pá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_usage
mientras se ejecuta la consulta muestra que parece estar constantemente asignando y desasignando páginas tempdb
con entre 1.800 y 3.000 páginas asignadas en cualquier momento.
fuente
WHERE
clá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_data
cadena suele ser grande, lo que hace que la conversión de cadena aXML
costosa. En planes lentos, la cadena deXML
conversión se realiza cada vez que un operador posterior que requiere el resultado delExpr1000
rebote.El rebobinado se produce en el lado interno de una unión de bucles anidados cuando cambia un parámetro correlacionado (referencia externa).
Expr1000
es 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 cadenaXML
puede ser fácilmente en millones.Las pilas de llamadas a continuación muestran ejemplos de la
target_data
cadena 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
XML
cada 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 1
se especifica la sugerencia. SiMAXDOP 1, LOOP JOIN
se 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
Expr1000
aparece 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 JOIN
arriba) también fuerzan el orden de toda la consulta, como si seFORCE ORDER
hubiera especificado. La inversión es necesaria para garantizar queExpr1000
aparezca 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
Expr1000
todaví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
Expr1000
ocurre 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
tempdb
tabla de trabajo para almacenar losXML
datos, y cada acceso al resultado de losExpr1000
operadores 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
tempdb
se 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
XML
manipulación puede ser algo difícil de optimizar hoy. EscribirXML
en 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_K
aparecer.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 ms
y3249 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.