sql >> Base de Datos >  >> RDS >> Database

Impacto del plan de ejecución en esperas ASYNC_NETWORK_IO:parte 1

Hace unas semanas, se hizo una pregunta interesante en la etiqueta hash #SQLHelp en Twitter sobre el impacto de los planes de ejecución en el tipo de espera ASYNC_NETWORK_IO, y generó algunas opiniones diferentes y mucha discusión.

https://twitter.com/shawndube/status/1225476846537650176

Mi respuesta inmediata a esto sería que alguien está malinterpretando la causa y el efecto de esto, ya que se encuentra el tipo de espera ASYNC_NETWORK_IO cuando el motor tiene resultados para enviar TDS al cliente pero no hay búferes TDS disponibles en la conexión para enviarlos sobre. En términos generales, esto significa que el lado del cliente no está consumiendo los resultados de manera eficiente, pero según la discusión subsiguiente, me intrigó lo suficiente como para hacer algunas pruebas de si un plan de ejecución realmente afectaría o no las esperas de ASYNC_NETWORK_IO de manera significativa.

Para resumir:Centrarse en ASYNC_NETWORK_IO espera solo como una métrica de ajuste es un error. Cuanto más rápido se ejecute una consulta, mayor será la probabilidad de que se acumule este tipo de espera, incluso si el cliente consume los resultados lo más rápido posible. (Consulte también la publicación reciente de Greg sobre cómo centrarse en las esperas solas en general).

Configuración de prueba

Para ejecutar las pruebas para esto, se generó una tabla muy simple basada en un ejemplo que me proporcionó por correo electrónico otro miembro de la comunidad, que demostraba un cambio en el tipo de espera, pero también tenía una consulta completamente diferente entre los dos. pruebas con una tabla adicional que se usa en la segunda prueba, y tenía un comentario para desactivar los resultados, lo que elimina la parte significativa de este tipo de espera para empezar, por lo que no es solo un cambio de plan.

Nota:me gustaría señalar que esta no es una declaración negativa hacia nadie en absoluto; la discusión subsiguiente y las pruebas adicionales que surgieron de la reproducción original que se proporcionó fueron muy educativas y llevaron a una mayor investigación para comprender este tipo de espera en general. La reproducción original SÍ demostró una diferencia, pero con cambios adicionales que no formaban parte de la pregunta original tal como se planteó.

DROP TABLE IF EXISTS [DemoTable];
 
CREATE TABLE [DemoTable] (
  ID INT PRIMARY KEY,
  FILLER VARCHAR(100)
);
 
INSERT INTO [DemoTable] WITH (TABLOCK)
SELECT TOP (250000) ROW_NUMBER() OVER (ORDER BY (SELECT NULL)), REPLICATE('Z', 50)
  FROM master..spt_values t1
  CROSS JOIN master..spt_values t2
  CROSS JOIN master..spt_values t3
  OPTION (MAXDOP 1);
GO

Usando esta tabla como un conjunto de datos base para probar diferentes formas de planos usando sugerencias, se usaron las siguientes consultas:

SELECT t1.ID, t2.FILLER, t2.FILLER
  FROM [DemoTable] t1
  INNER HASH JOIN [DemoTable] t2 ON t1.ID = t2.ID;
 
  SELECT t1.ID, t2.FILLER, t2.FILLER
  FROM [DemoTable] t1
  INNER MERGE JOIN [DemoTable] t2 ON t1.ID = t2.ID;
 
  SELECT t1.ID, t2.FILLER, t2.FILLER
  FROM [DemoTable] t1
  INNER LOOP JOIN [DemoTable] t2 ON t1.ID = t2.ID;

Dado que estaba ejecutando estas consultas en SQL Server 2019 CU1, los planes de ejecución incluían la información de las estadísticas de espera reales relacionadas con la ejecución de la consulta.

Nota: El optimizador usaría un Merge Join sin que se aplicaran las sugerencias para este conjunto de datos y consulta específicos.

Resultados de la prueba inicial

Para las pruebas iniciales, simplemente usé SSMS para ejecutar las consultas y recopilé el plan de ejecución real para comparar la información de espera asociada con cada consulta que se muestra a continuación. Tenga en cuenta que para este tamaño de datos, los tiempos transcurridos no son significativamente diferentes, como tampoco lo son los tiempos de espera ni los recuentos de espera para ASYNC_NETWORK_IO.

HASH ÚNETE

<WaitStats>
  <Wait WaitType="CXPACKET"         WaitTimeMs="18393" WaitCount="8415" />
  <Wait WaitType="ASYNC_NETWORK_IO" WaitTimeMs="4394"  WaitCount="6635" />
  <Wait WaitType="HTDELETE"         WaitTimeMs="957"   WaitCount="6"    />
  <Wait WaitType="HTBUILD"          WaitTimeMs="4"     WaitCount="6"    />
  <Wait WaitType="HTREPARTITION"    WaitTimeMs="3"     WaitCount="6"    />
  <Wait WaitType="CMEMTHREAD"       WaitTimeMs="3"     WaitCount="14"   />
  <Wait WaitType="LATCH_EX"         WaitTimeMs="2"     WaitCount="8"    />
</WaitStats>
<QueryTimeStats CpuTime="1068" ElapsedTime="4961" />

COMBINAR UNIRSE

<WaitStats>
  <Wait WaitType="ASYNC_NETWORK_IO" WaitTimeMs="3169" WaitCount="6592" />
</WaitStats>
<QueryTimeStats CpuTime="792" ElapsedTime="3933" />

UNIR EN BUCLE

<WaitStats>
  <Wait WaitType="CXPACKET"         WaitTimeMs="13690" WaitCount="8286" />
  <Wait WaitType="ASYNC_NETWORK_IO" WaitTimeMs="3576"  WaitCount="6631" />
  <Wait WaitType="LATCH_EX"         WaitTimeMs="1"     WaitCount="3"    />
</WaitStats>
<QueryTimeStats CpuTime="2172" ElapsedTime="4084" />

Sin embargo, aquí no quería dejar de probar, porque mi propia experiencia ha demostrado repetidamente que Management Studio es un consumidor muy ineficiente de resultados de SQL Server y puede en sí mismo causar ASYNC_NETWORK_IO espera a ocurrir. Entonces, decidí cambiar la forma en que estaba probando las cosas y fui a una ejecución SQLCMD de las consultas.

Pruebas con SQLCMD

Como utilizo mucho SQLCMD para demostraciones mientras hago presentaciones, creé un archivo testscript.sql con el siguiente contenido:

PRINT 'Minimize Screen';
GO
 
WAITFOR DELAY '00:00:05';
GO
 
SELECT t1.ID, t2.FILLER, t2.FILLER
  FROM [DemoTable] t1
  INNER HASH JOIN [DemoTable] t2 ON t1.ID = t2.ID;
GO
 
SELECT t1.ID, t2.FILLER, t2.FILLER
  FROM [DemoTable] t1
  INNER MERGE JOIN [DemoTable] t2 ON t1.ID = t2.ID;
GO
 
SELECT t1.ID, t2.FILLER, t2.FILLER
  FROM [DemoTable] t1
  INNER LOOP JOIN [DemoTable] t2 ON t1.ID = t2.ID;
GO

Esto se ejecutó desde la línea de comando de la siguiente manera, y durante el retraso de 5 segundos, la ventana se minimizó para permitir que la ejecución no renderizara ni desplazara los resultados durante el procesamiento:

sqlcmd -S.\SQL2019 -i testscript.sql -dAdventureWorks2017

Para capturar los planes de ejecución reales, opté por una sesión de eventos extendidos que recopilaba el evento query_post_execution_showplan que, en retrospectiva, en SQL Server 2019, pensé que debería haber usado query_post_execution_plan_profile en su lugar para usar la implementación ligera de la infraestructura de creación de perfiles de estadísticas de ejecución de consultas v3, pero este evento no devuelve la información de WaitStats o QueryTimeStats a menos que query_post_execution_showplan también esté habilitado al mismo tiempo. Además, dado que se trata de una máquina de prueba aislada sin otra carga de trabajo, los impactos de la creación de perfiles estándar no son realmente una gran preocupación aquí.

CREATE EVENT SESSION [Actual Plan] ON SERVER 
  ADD EVENT sqlserver.query_post_execution_showplan
  (ACTION(sqlserver.session_id));

HASH ÚNETE

<WaitStats>
  <Wait WaitType="CXPACKET"         WaitTimeMs="45722" WaitCount="8674" />
  <Wait WaitType="ASYNC_NETWORK_IO" WaitTimeMs="11321" WaitCount="6610" />
  <Wait WaitType="HTDELETE"         WaitTimeMs="1174"  WaitCount="6"    />
  <Wait WaitType="HTREPARTITION"    WaitTimeMs="4"     WaitCount="6"    />
  <Wait WaitType="HTBUILD"          WaitTimeMs="3"     WaitCount="5"    />
  <Wait WaitType="LATCH_EX"         WaitTimeMs="2"     WaitCount="7"    />
</WaitStats>
<QueryTimeStats ElapsedTime="11874" CpuTime="1070" />

COMBINAR UNIRSE

<WaitStats>
  <Wait WaitType="ASYNC_NETWORK_IO" WaitTimeMs="10837" WaitCount="6602" />
</WaitStats>
<QueryTimeStats ElapsedTime="11597" CpuTime="789" />

UNIR EN BUCLE

<WaitStats>
  <Wait WaitType="CXPACKET"         WaitTimeMs="43587" WaitCount="8620" />
  <Wait WaitType="ASYNC_NETWORK_IO" WaitTimeMs="11177" WaitCount="6612" />
  <Wait WaitType="LATCH_EX"         WaitTimeMs="1"     WaitCount="3"    />
</WaitStats>
<QueryTimeStats ElapsedTime="11696" CpuTime="2221" />

En realidad, esto no resultó ser una forma más rápida de ejecutar la consulta y, en realidad, el rendimiento se redujo al usar la utilidad de línea de comandos para ejecutar la consulta, incluso cuando la ventana está minimizada y no se desplazan visiblemente los resultados. Con la ventana abierta, el tiempo de ejecución de HASH fue de 15708 ms y el tiempo de espera de ASYNC_NETWORK_IO fue de 15126 ms. Sin embargo, esto demuestra que para los mismos resultados exactos, el rendimiento del cliente que consume los resultados afecta tanto el tiempo de espera como el tiempo de ejecución de la consulta.

¿Impacto del paralelismo?

Una de las cosas que noté fue que solo dos de los planes se habían ejecutado con paralelismo, según la existencia de las esperas CXPACKET y LATCH_EX en el plan de ejecución XML. Así que me preguntaba qué tipo de impacto tendría forzar un plan de ejecución en serie en la ejecución de estas mismas consultas usando OPCIÓN (MAXDOP 1).

HASH ÚNETE

<WaitStats>
  <Wait WaitType="ASYNC_NETWORK_IO" WaitTimeMs="4047" WaitCount="6379" />
</WaitStats>
<QueryTimeStats CpuTime="602" ElapsedTime="4619" />

COMBINAR UNIRSE

<WaitStats>
  <Wait WaitType="ASYNC_NETWORK_IO" WaitTimeMs="3699" WaitCount="6608" />
</WaitStats>
<QueryTimeStats CpuTime="810" ElapsedTime="4478" />

UNIR EN BUCLE

<WaitStats>
  <Wait WaitType="ASYNC_NETWORK_IO" WaitTimeMs="2083" WaitCount="5385" />
</WaitStats>
<QueryTimeStats CpuTime="1859" ElapsedTime="3918" />

Observe aquí que los recuentos generales de espera no han disminuido significativamente. Solo el plan de unión de bucle en serie tiene un cambio importante en la cantidad de esperas o la cantidad total de tiempo de espera asociado con él, y de forma aislada esto no significa que sea un beneficio positivo, el tiempo de ejecución de la consulta no mejoró significativamente y puede haber otros factores que afectaron los resultados de esa prueba específica.

La siguiente tabla resume el tiempo de espera de ASYNC_NETWORK_IO y cuenta para cada una de las pruebas.

TipoPlan Filas Número de esperas Tiempo de espera Tiempo de ejecución Nombre de la aplicación MAXDOP 1 Paralelo
Hash 250.000 6635 4394 4961 SSMS N Y
Fusionar 250.000 6592 3169 3933 SSMS N N
Bucle 250.000 6631 3576 4084 SSMS N Y
Hash 250.000 6610 11.321 11.874 SQLCMD N Y
Fusionar 250.000 6602 10.837 11.597 SQLCMD N N
Bucle 250.000 6612 11.177 11.696 SQLCMD N Y
Hash 250.000 6379 4047 4619 SSMS Y N
Fusionar 250.000 6608 3699 4479 SSMS Y N
Bucle 250.000 5385 2083 3918 SSMS Y N

Resumen

Si bien la investigación de esta publicación no cubre todos los aspectos de los cambios de plan o el tipo de espera ASYNC_NETWORK_IO, sí demuestra que esta espera no se ve afectada significativamente por el plan de ejecución que se usa para la ejecución de una consulta. Clasificaría este tipo de espera casi como el tipo de espera CXPACKET al realizar el análisis de un servidor como un todo; es normal para la mayoría de las cargas de trabajo y, a menos que esté increíblemente sesgado y haya otros problemas de rendimiento que apunten a un consumo lento de resultados por parte de los clientes, como bloquear con el bloqueador principal esperando ASYNC_NETWORK_IO, entonces hay algo que debe ignorarse como solo 'parte de la firma de espera normal para la carga de trabajo'.