He estado tratando de diagnosticar retrasos en una aplicación. Para esto, he registrado los eventos extendidos de SQL Server .
- Para esta pregunta, estoy mirando un procedimiento almacenado en particular.
- Pero hay un conjunto básico de una docena de procedimientos almacenados que igualmente pueden usarse como una investigación de manzanas con manzanas.
- y cada vez que ejecuto manualmente uno de los procedimientos almacenados, siempre se ejecuta rápidamente
- y si un usuario lo intenta nuevamente: se ejecutará rápido.
Los tiempos de ejecución del procedimiento almacenado varían enormemente. Muchas de las ejecuciones de este procedimiento almacenado regresan en <1s:
Y para ese cubo "rápido" , es mucho menos de 1s. En realidad son alrededor de 90 ms:
Pero hay una larga cola de usuarios que tienen que esperar 2s, 3s, 4s segundos. Algunos tienen que esperar 12s, 13s, 14s. Luego están las almas realmente pobres que tienen que esperar 22s, 23s, 24s.
Y después de 30 segundos , la aplicación cliente se da por vencida, cancela la consulta y el usuario tuvo que esperar 30 segundos .
Correlación para encontrar causalidad
Entonces intenté correlacionar:
- duración vs lecturas lógicas
- duración vs lecturas físicas
- duración vs tiempo de la CPU
Y ninguno parece dar ninguna correlación; ninguno parece ser la causa
duración frente a lecturas lógicas : ya sea una pequeña o muchas lecturas lógicas, la duración todavía fluctúa enormemente :
duración frente a lecturas físicas : incluso si la consulta no se realizó desde la memoria caché y se necesitaban muchas lecturas físicas, no afecta la duración:
duración frente al tiempo de CPU : si la consulta tomó 0 segundos de tiempo de CPU o 2,5 segundos completos de tiempo de CPU, las duraciones tienen la misma variabilidad:
Bonificación : noté que la Duración v Lecturas físicas y la Duración v Tiempo de CPU son muy similares. Esto se prueba si trato de correlacionar el tiempo de CPU con las lecturas físicas:
Resulta que gran parte del uso de la CPU proviene de E / S. ¡Quien sabe!
Entonces, si no hay nada en el acto de ejecutar la consulta que pueda explicar las diferencias en el tiempo de ejecución, ¿eso implica que es algo no relacionado con la CPU o el disco duro?
Si la CPU o el disco duro fueran el cuello de botella; ¿No sería el cuello de botella?
Si planteamos la hipótesis de que fue la CPU el cuello de botella; que la CPU tiene poca potencia para este servidor:
- entonces, ¿las ejecuciones con más tiempo de CPU no tomarían más tiempo?
- ya que tienen que completar con otros usando la CPU sobrecargada?
Del mismo modo para los discos duros. Si planteamos la hipótesis de que el disco duro era un cuello de botella; que los discos duros no tienen suficiente rendimiento aleatorio para este servidor:
- entonces, ¿las ejecuciones usando más lecturas físicas no tomarían más tiempo?
- ¿ya que tienen que completar con otros que usan la E / S del disco duro sobrecargado?
El procedimiento almacenado en sí no realiza ni requiere ninguna escritura.
- Por lo general, devuelve 0 filas (90%).
- Ocasionalmente devolverá 1 fila (7%).
- En raras ocasiones devolverá 2 filas (1.4%).
- Y en el peor de los casos, ha devuelto más de 2 filas (una vez devolviendo 12 filas)
Por lo tanto, no es como si estuviera devolviendo un volumen loco de datos.
Uso de CPU del servidor
El uso promedio del procesador del servidor es de aproximadamente 1.8%, con un pico ocasional de hasta 18%, por lo que no parece que la carga de la CPU sea un problema:
Entonces la CPU del servidor no parece sobrecargada.
Pero el servidor es virtual ...
¿Algo fuera del universo?
Lo único que me queda por imaginar es algo que existe fuera del universo del servidor.
- si no es lecturas lógicas
- y no son lecturas físicas
- y no es uso de CPU
- y no es carga de CPU
Y no es como si fueran los parámetros del procedimiento almacenado (porque emitir la misma consulta manualmente y no toma 27 segundos, toma ~ 0 segundos).
¿Qué más podría explicar que el servidor a veces demore 30 segundos, en lugar de 0 segundos, para ejecutar el mismo procedimiento almacenado compilado?
- puntos de control?
Es un servidor virtual
- el host sobrecargado?
- otra VM en el mismo host?
Pasando por los eventos extendidos del servidor; no sucede nada más en particular cuando una consulta de repente toma 20 segundos. Funciona bien, luego decide no funcionar bien:
- 2 segundos
- 1 segundo
- 30 segundos
- 3 segundos
- 2 segundos
Y no hay otros artículos particularmente extenuantes que pueda encontrar. No es durante la copia de seguridad del registro de transacciones de cada 2 horas.
¿Qué más podría ser?
¿Hay algo que pueda decir además de "el servidor" ?
Editar : correlacionar por hora del día
Me di cuenta de que he correlacionado las duraciones con todo:
- lecturas lógicas
- lecturas físicas
- uso de CPU
Pero la única cosa con la que no lo relacioné fue la hora del día . Quizás la copia de seguridad del registro de transacciones cada 2 horas sea un problema.
O tal vez la desaceleración no se producen en los puestos de control durante mandriles?
No:
Intel Xeon Gold de cuatro núcleos 6142.
Editar: las personas hipotizan el plan de ejecución de consultas
Las personas plantean la hipótesis de que los planes de ejecución de consultas deben ser diferentes entre "rápido" y "lento". Ellos no son.
Y podemos ver esto inmediatamente de la inspección.
Sabemos que la mayor duración de la pregunta no se debe a un plan de ejecución "deficiente":
- uno que tomó más lecturas lógicas
- uno que consumió más CPU de más uniones y búsquedas clave
Porque si un aumento en las lecturas, o un aumento en la CPU, fue la causa de una mayor duración de la consulta, entonces ya lo habríamos visto anteriormente. No hay correlación
Pero intentemos correlacionar la duración con la métrica del producto del área de lecturas de CPU:
Hay una correlación aún menor, lo cual es una paradoja.
Editar : se actualizaron los diagramas de dispersión para solucionar un error en los diagramas de dispersión de Excel con grandes cantidades de valores.
Próximos pasos
Mis próximos pasos serán hacer que alguien tenga que generar eventos en el servidor para consultas bloqueadas , después de 5 segundos:
EXEC sp_configure 'blocked process threshold', '5';
RECONFIGURE
No explicará si las consultas se bloquean durante 4 segundos. Pero quizás cualquier cosa que esté bloqueando una consulta durante 5 segundos también bloquea algo durante 4 segundos.
Los planes lentos
Aquí está el plan lento de los dos procedimientos almacenados que se ejecutan:
- `EJECUTAR FindFrob @CustomerID = 7383, @StartDate = '20190725 04: 00: 00.000', @EndDate = '20190726 04: 00: 00.000'
- `EJECUTAR FindFrob @CustomerID = 7383, @StartDate = '20190725 04: 00: 00.000', @EndDate = '20190726 04: 00: 00.000'
El mismo procedimiento almacenado, con los mismos parámetros, se ejecuta de forma consecutiva:
| Duration (us) | CPU time (us) | Logical reads | Physical reads |
|---------------|---------------|---------------|----------------|
| 13,984,446 | 47,000 | 5,110 | 771 |
| 4,603,566 | 47,000 | 5,126 | 740 |
Llamada 1:
|--Nested Loops(Left Semi Join, OUTER REFERENCES:([Contoso2].[dbo].[Frobs].[FrobGUID]) OPTIMIZED)
|--Nested Loops(Inner Join, OUTER REFERENCES:([Contoso2].[dbo].[FrobTransactions].[OnFrobGUID]))
| |--Nested Loops(Inner Join, OUTER REFERENCES:([Contoso2].[dbo].[FrobTransactions].[RowNumber]) OPTIMIZED)
| | |--Nested Loops(Inner Join, OUTER REFERENCES:([tpi].[TransactionGUID]) OPTIMIZED)
| | | |--Nested Loops(Inner Join, OUTER REFERENCES:([tpi].[TransactionGUID]) OPTIMIZED)
| | | | |--Index Seek(OBJECT:([Contoso2].[dbo].[TransactionPatronInfo].[IX_TransactionPatronInfo_CustomerID_TransactionGUID] AS [tpi]), SEEK:([tpi].[CustomerID]=[@CustomerID]) ORDERED FORWARD)
| | | | |--Index Seek(OBJECT:([Contoso2].[dbo].[Transactions].[IX_Transactions_TransactionGUIDTransactionDate]), SEEK:([Contoso2].[dbo].[Transactions].[TransactionGUID]=[Contoso2].[dbo
| | | |--Index Seek(OBJECT:([Contoso2].[dbo].[FrobTransactions].[IX_FrobTransactions2_MoneyAppearsOncePerTransaction]), SEEK:([Contoso2].[dbo].[FrobTransactions].[TransactionGUID]=[Contos
| | |--Clustered Index Seek(OBJECT:([Contoso2].[dbo].[FrobTransactions].[IX_FrobTransactions_RowNumber]), SEEK:([Contoso2].[dbo].[FrobTransactions].[RowNumber]=[Contoso2].[dbo].[Fin
| |--Clustered Index Seek(OBJECT:([Contoso2].[dbo].[Frobs].[PK_Frobs_FrobGUID]), SEEK:([Contoso2].[dbo].[Frobs].[FrobGUID]=[Contoso2].[dbo].[FrobTransactions].[OnFrobGUID]), WHERE:([Contos
|--Filter(WHERE:([Expr1009]>(1)))
|--Compute Scalar(DEFINE:([Expr1009]=CONVERT_IMPLICIT(int,[Expr1012],0)))
|--Stream Aggregate(DEFINE:([Expr1012]=Count(*)))
|--Index Seek(OBJECT:([Contoso2].[dbo].[FrobTransactions].[IX_FrobTransactins_OnFrobGUID]), SEEK:([Contoso2].[dbo].[FrobTransactions].[OnFrobGUID]=[Contoso2].[dbo].[Frobs].[LC
Llamada 2
|--Nested Loops(Left Semi Join, OUTER REFERENCES:([Contoso2].[dbo].[Frobs].[FrobGUID]) OPTIMIZED)
|--Nested Loops(Inner Join, OUTER REFERENCES:([Contoso2].[dbo].[FrobTransactions].[OnFrobGUID]))
| |--Nested Loops(Inner Join, OUTER REFERENCES:([Contoso2].[dbo].[FrobTransactions].[RowNumber]) OPTIMIZED)
| | |--Nested Loops(Inner Join, OUTER REFERENCES:([tpi].[TransactionGUID]) OPTIMIZED)
| | | |--Nested Loops(Inner Join, OUTER REFERENCES:([tpi].[TransactionGUID]) OPTIMIZED)
| | | | |--Index Seek(OBJECT:([Contoso2].[dbo].[TransactionPatronInfo].[IX_TransactionPatronInfo_CustomerID_TransactionGUID] AS [tpi]), SEEK:([tpi].[CustomerID]=[@CustomerID]) ORDERED FORWARD)
| | | | |--Index Seek(OBJECT:([Contoso2].[dbo].[Transactions].[IX_Transactions_TransactionGUIDTransactionDate]), SEEK:([Contoso2].[dbo].[Transactions].[TransactionGUID]=[Contoso2].[dbo
| | | |--Index Seek(OBJECT:([Contoso2].[dbo].[FrobTransactions].[IX_FrobTransactions2_MoneyAppearsOncePerTransaction]), SEEK:([Contoso2].[dbo].[FrobTransactions].[TransactionGUID]=[Contos
| | |--Clustered Index Seek(OBJECT:([Contoso2].[dbo].[FrobTransactions].[IX_FrobTransactions_RowNumber]), SEEK:([Contoso2].[dbo].[FrobTransactions].[RowNumber]=[Contoso2].[dbo].[Fin
| |--Clustered Index Seek(OBJECT:([Contoso2].[dbo].[Frobs].[PK_Frobs_FrobGUID]), SEEK:([Contoso2].[dbo].[Frobs].[FrobGUID]=[Contoso2].[dbo].[FrobTransactions].[OnFrobGUID]), WHERE:([Contos
|--Filter(WHERE:([Expr1009]>(1)))
|--Compute Scalar(DEFINE:([Expr1009]=CONVERT_IMPLICIT(int,[Expr1012],0)))
|--Stream Aggregate(DEFINE:([Expr1012]=Count(*)))
|--Index Seek(OBJECT:([Contoso2].[dbo].[FrobTransactions].[IX_FrobTransactins_OnFrobGUID]), SEEK:([Contoso2].[dbo].[FrobTransactions].[OnFrobGUID]=[Contoso2].[dbo].[Frobs].[LC
Tiene sentido que los planes sean idénticos; está ejecutando el mismo procedimiento almacenado, con los mismos parámetros.