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

Capturar advertencias del plan de ejecución mediante eventos extendidos

Estamos enseñando IEPTO2 en Dublín esta semana (y si Irlanda no está en tu lista de lugares para ver en esta vida, debes agregarlo... es fantástico aquí) y hoy terminé el módulo de análisis de planes de consultas. Una cosa que cubro son las cosas interesantes que puede encontrar en el plan de consulta, por ejemplo:

  • NoJoinPredicate (2005 y superior)
  • Columnas sin estadísticas (2005 y posteriores)
  • Índices no coincidentes (2008 y posteriores)
  • PlanAffectingConvert (2012 y posterior)

Estos atributos son buenos para buscar cuando está mirando un solo plan, o un conjunto de planes, mientras está ajustando. Pero si quieres ser un poco más proactivo, puedes comenzar a extraer el caché del plan y buscarlos allí. Por supuesto, hacerlo requiere escribir algo de XQuery, ya que los planes son XML (para obtener detalles sobre el esquema del plan de presentación, consulte:http://schemas.microsoft.com/sqlserver/2004/07/showplan/). No me encanta XML, aunque no por falta de intentos, y cuando uno de los asistentes preguntó si podía capturar consultas que tuvieran el atributo NoJoinPredicate a través de eventos extendidos, pensé:"Qué gran idea, tendré que comprobar .”

Efectivamente, hay un evento para eso. Hay un evento para los cuatro que mencioné anteriormente:

  • missing_join_predicate
  • missing_column_statistics
  • índices_filtrados_sin_coincidencia
  • plan_affecting_convert

Lindo. Configurarlos en una sesión de eventos extendidos es bastante sencillo. En este caso, recomendaría usar el objetivo event_file, ya que probablemente iniciará la sesión del evento y la dejará correr un poco antes de regresar y revisar el resultado. He incluido algunas acciones, con la esperanza de que estos eventos no se disparen que a menudo, por lo que no estamos agregando demasiados gastos generales aquí. Incluí sql_text a pesar de que no es una acción en la que realmente deba confiar. Jonathan ha discutido esto antes, pero sql_text solo le brinda el búfer de entrada, por lo que es posible que no obtenga la historia completa de la consulta. Por esa razón, también incluí plan_handle. Con la salvedad de que, dependiendo de cuándo vayas a buscar el plan, es posible que ya no esté en la memoria caché del plan.

-- Remove event session if it exists
IF EXISTS (SELECT 1 FROM [sys].[server_event_sessions]
WHERE [name] = 'InterestingPlanEvents')
BEGIN
  DROP EVENT SESSION [InterestingPlanEvents] ON SERVER
END
GO
 
-- Define event session
CREATE EVENT SESSION [InterestingPlanEvents]
ON SERVER
ADD EVENT sqlserver.missing_column_statistics
(
  ACTION(sqlserver.database_id,sqlserver.plan_handle,sqlserver.sql_text)
  WHERE ([package0].[equal_boolean]([sqlserver].[is_system],(0))
    AND [sqlserver].[database_id]>(4))
),
ADD EVENT sqlserver.missing_join_predicate
(
  ACTION(sqlserver.database_id,sqlserver.plan_handle,sqlserver.sql_text)
  WHERE ([sqlserver].[is_system]=(0) AND [sqlserver].[database_id]>(4))
),
ADD EVENT sqlserver.plan_affecting_convert
(
  ACTION(sqlserver.database_id,sqlserver.plan_handle,sqlserver.sql_text)
  WHERE ([package0].[equal_boolean]([sqlserver].[is_system],(0))
    AND [sqlserver].[database_id]>(4))
),
ADD EVENT sqlserver.unmatched_filtered_indexes
(
  ACTION(sqlserver.plan_handle,sqlserver.sql_text)
  WHERE ([package0].[equal_boolean]([sqlserver].[is_system],(0))
    AND [sqlserver].[database_id]>(4))
)
ADD TARGET package0.event_file
(
  SET filename=N'C:\temp\InterestingPlanEvents' /* change location if appropriate */
)
WITH (MAX_MEMORY=4096 KB,EVENT_RETENTION_MODE=ALLOW_SINGLE_EVENT_LOSS,
MAX_DISPATCH_LATENCY=5 SECONDS,MAX_EVENT_SIZE=0 KB,MEMORY_PARTITION_MODE=NONE,
TRACK_CAUSALITY=ON,STARTUP_STATE=OFF)
GO
 
-- Start the event session
ALTER EVENT SESSION [InterestingPlanEvents] ON SERVER STATE=START;
GO

Una vez que tengamos la sesión de eventos en funcionamiento, podemos generar estos eventos con el código de muestra a continuación. Tenga en cuenta que este código supone una instalación nueva de AdventureWorks2014. Si no tiene uno, es posible que no vea que se activa el evento de estadísticas_columna_faltante si consulta la columna [Fecha de contratación] en [Recursos humanos]. [Empleado].

-- These queries assume a FRESH restore of AdventureWorks2014
ALTER DATABASE [AdventureWorks2014] SET AUTO_CREATE_STATISTICS OFF;
GO
 
USE [AdventureWorks2014];
GO
 
CREATE INDEX [NCI_SalesOrderHeader] ON [Sales].[SalesOrderHeader] (
[PurchaseOrderNumber], [CustomerID], [TotalDue], [DueDate]
)
WHERE [SubTotal] > 10000.00;
GO
 
/*
No join predicate
NOTE: We clear procedure here because the event ONLY fires for the *initial* compilation
*/
DBCC FREEPROCCACHE; /* Not for production use */
 
SELECT [h].[SalesOrderID], [d].[SalesOrderDetailID], [h].[CustomerID]
FROM [Sales].[SalesOrderDetail] [d],
[Sales].[SalesOrderHeader] [h]
WHERE [d].[ProductID] = 897;
GO
 
-- Columns with no statistics
SELECT [BusinessEntityID], [NationalIDNumber], [JobTitle], [HireDate], [ModifiedDate]
FROM [HumanResources].[Employee]
WHERE [HireDate] >= '2013-01-01';
GO
 
-- Unmatched Index
DECLARE @Total MONEY = 10000.00;
 
SELECT [PurchaseOrderNumber], [CustomerID], [TotalDue], [DueDate]
FROM [Sales].[SalesOrderHeader]
WHERE [SubTotal] > @Total;
GO
 
-- Plan Affecting Convert
SELECT [BusinessEntityID], [NationalIDNumber], [JobTitle], [HireDate], [ModifiedDate]
FROM [HumanResources].[Employee]
WHERE [NationalIDNumber] = 345106466;
GO
 
ALTER EVENT SESSION [InterestingPlanEvents]
ON SERVER
STATE=STOP;
GO
 
DROP EVENT SESSION [InterestingPlanEvents]
ON SERVER;
GO

NOTA:DESPUÉS de que haya terminado de extraer los planes del caché, puede ejecutar la instrucción ALTER para habilitar la opción de creación automática de estadísticas. Si lo hace en este punto, se borrará el caché del plan y tendrá que comenzar de nuevo con la prueba. (Y también espere hasta que haya terminado para soltar el índice).

ALTER DATABASE [AdventureWorks2014] SET AUTO_CREATE_STATISTICS ON;
GO
 
DROP INDEX [NCI_SalesOrderHeader] ON [Sales].[SalesOrderHeader];
GO

Como detuve la sesión del evento, abriré el archivo de salida en SSMS para ver lo que capturamos:

Resultado de eventos extendidos

Para nuestra primera consulta en la que falta un predicado de combinación, aparece un evento y puedo ver el texto de la consulta en el campo sql_text. Sin embargo, lo que realmente quiero es ver el plan también, así que puedo tomar plan_handle y verificar sys.dm_exec_query_plan:

SELECT query_plan FROM sys.dm_exec_query_plan
(0x06000700E2200333405DD12C0000000001000000000000000000000000000000000000000000000000000000);

Y abriendo eso en SQL Sentry Plan Explorer:

Predicado de unión faltante

El plan tiene un indicador visual del predicado de combinación faltante en el ciclo anidado (la X roja), y si paso el mouse sobre él, veo la advertencia (y está en el XML para el plan). Excelente... Ahora puedo ir a hablar con mis desarrolladores sobre la reescritura de esta consulta.

El siguiente evento es para una estadística de columna faltante. Forcé por completo esta situación al desactivar AUTO_CREATE_STATISTICS para la base de datos AdventureWorks2014. No recomiendo esto de ninguna manera, forma o forma. Esta opción viene habilitada por defecto y recomiendo dejarla siempre habilitada. Sin embargo, apagarlo es la forma más fácil de generar este evento. Nuevamente tengo la consulta en el campo sql_text, pero usaré plan_handle nuevamente para extraer el plan:

SELECT query_plan FROM sys.dm_exec_query_plan
(0x060007004448323810921C360000000001000000000000000000000000000000000000000000000000000000);

Estadística faltante

Y nuevamente tenemos una señal visual (el triángulo amarillo con el signo de exclamación) para indicar que hay un problema con el plan, y nuevamente está en el XML. A partir de aquí, primero verificaría si AUTO_CREATE_STATISTICS está deshabilitado, y si no, comenzaría a ejecutar la consulta en Management Studio para ver si puedo recrear la advertencia (y obligar a las estadísticas a crear).

Ahora, los eventos restantes son un poco más interesantes.

Notará que tenemos tres eventos de índices_filtrados_sin_coincidencia. Todavía tengo que determinar por qué, pero estoy trabajando en ello y lo publicaré en los comentarios si/cuando lo solucione. Por ahora basta con que tenga el evento, y dentro del evento también podemos ver información del objeto para saber el índice en cuestión:

Índice NCI_SalesOrderHeader al que hace referencia el evento de índice faltante

Y puedo volver a tomar plan_handle para encontrar el plan de consulta:

Índice no coincidente

Esta vez veo la advertencia en el operador SELECCIONAR, así que sé que hay algo que debo investigar más a fondo. En este caso, tiene opciones para hacer que el optimizador use el índice filtrado cuando usa parámetros, y le recomiendo leer la publicación de Aaron para obtener más información sobre el uso de índices filtrados.

Por último, tenemos nueve eventos para plan_affecting_convert. ¿Que demonios? Todavía estoy descifrando esto, pero usé la opción Rastrear causalidad para mi sesión de eventos (cuando estaba probando) para confirmar que todos los eventos son parte de la misma tarea (lo son). Si observa el elemento de expresión en el resultado, verá que cambia ligeramente (al igual que compile_time), y esto aparece cuando observa los detalles de la advertencia en el Explorador de planes de SQL Sentry (vea la segunda captura de pantalla a continuación). Dentro de la salida del evento, el elemento de expresión does díganos qué columna está involucrada, que es un comienzo pero no es suficiente información, por lo que nuevamente debemos obtener el plan:

SELECT query_plan FROM sys.dm_exec_query_plan
(0x0600070023747010E09E1C360000000001000000000000000000000000000000000000000000000000000000);

Plan que afecta a Convert

Detalle de conversión del plan

Volvemos a ver a nuestro amigo, el triángulo amarillo, en el operador SELECT, y dentro del XML podemos encontrar el atributo PlanAffectingConvert. Este atributo se agregó en el esquema del plan de presentación de SQL Server 2012, por lo que si está ejecutando una versión anterior, no verá esto en el plan. Resolver esta advertencia puede requerir un poco más de trabajo:debe comprender dónde tiene una discrepancia en el tipo de datos y por qué, y luego comenzar a modificar el código o el esquema... ambos pueden encontrar resistencia. Jonathan tiene una publicación que analiza la conversión implícita con más detalle, que es un buen lugar para comenzar si no ha trabajado anteriormente con problemas de conversión.

Resumen

La biblioteca de eventos de eventos extendidos continúa creciendo, y una cosa a considerar al solucionar problemas en SQL Server es si puede obtener la información que está buscando de otra manera. Tal vez porque es más fácil (¡prefiero XE a XML!), o porque es más eficiente o le brinda más detalles. Ya sea que esté buscando de forma proactiva problemas de consulta en su entorno, o reaccionando a un problema que alguien ha informado pero tiene problemas para encontrarlo, los eventos extendidos son una opción viable a considerar, particularmente a medida que se agregan más características nuevas a SQL Server.