Planes de Ejecución
Es más complicado de lo que cabría esperar a partir de la información proporcionada en los planes de ejecución si una instrucción SQL utiliza parametrización simple . No sorprende que incluso los usuarios de SQL Server con mucha experiencia tiendan a equivocarse, dada la información contradictoria que a menudo se nos proporciona.
Veamos algunos ejemplos que usan la base de datos Stack Overflow 2010 en SQL Server 2019 CU 14, con la compatibilidad de la base de datos configurada en 150.
Para comenzar, necesitaremos un nuevo índice no agrupado:
CREATE INDEX [IX dbo.Users Reputation (DisplayName)] ON dbo.Users (Reputation) INCLUDE (DisplayName);
1. Parametrización simple aplicada
Esta primera consulta de ejemplo utiliza parametrización simple :
SELECT U.DisplayName FROM dbo.Users AS U WHERE U.Reputation = 999;
El estimado (pre-ejecución) plan tiene los siguientes elementos relacionados con la parametrización:
Propiedades de parametrización del plan estimado
Observe el @1
El parámetro se introduce en todas partes excepto en el texto de consulta que se muestra en la parte superior.
El real (posterior a la ejecución) el plan tiene:
Propiedades de parametrización del plan real
Observe que la ventana de propiedades ahora ha perdido el ParameterizedText
mientras obtiene información sobre el valor de tiempo de ejecución del parámetro. El texto de consulta parametrizado ahora se muestra en la parte superior de la ventana con '@1
' en lugar de '999'.
2. Parametrización simple no aplicada
Este segundo ejemplo no use una parametrización simple:
-- Projecting an extra column SELECT U.DisplayName, U.CreationDate -- NEW FROM dbo.Users AS U WHERE U.Reputation = 999;
El estimado el plan muestra:
Plan estimado no parametrizado
Esta vez, el parámetro @1
falta en la búsqueda de índice información sobre herramientas, pero el texto parametrizado y otros elementos de la lista de parámetros son los mismos que antes.
Veamos el real plan de ejecución:
Plan real no parametrizado
Los resultados son los mismos que los reales parametrizados anteriores plan, excepto ahora la búsqueda de índice La información sobre herramientas muestra el valor no parametrizado '999'. El texto de consulta que se muestra en la parte superior utiliza @1
marcador de parámetro La ventana de propiedades también usa @1
y muestra el valor de tiempo de ejecución del parámetro.
La consulta no es una declaración parametrizada a pesar de toda la evidencia de lo contrario.
3. Error de parametrización
Mi tercer ejemplo también es no parametrizado por el servidor:
-- LOWER function used SELECT U.DisplayName, LOWER(U.DisplayName) FROM dbo.Users AS U WHERE U.Reputation = 999;
El estimado el plan es:
Error en la parametrización del plan estimado
No se menciona un @1
parámetro en cualquier lugar ahora, y la Lista de parámetros falta la sección de la ventana de propiedades.
El real el plan de ejecución es el mismo, así que no me molestaré en mostrarlo.
4. Plan Parametrizado Paralelo
Quiero mostrarles un ejemplo más usando paralelismo en el plan de ejecución. El bajo costo estimado de mis consultas de prueba significa que debemos reducir el umbral de costo para el paralelismo a 1:
EXECUTE sys.sp_configure @configname = 'cost threshold for parallelism', @configvalue = 1; RECONFIGURE;
El ejemplo es un poco más complejo esta vez:
SELECT U.DisplayName FROM dbo.Users AS U WHERE U.Reputation >= 5 AND U.DisplayName > N'ZZZ' ORDER BY U.Reputation DESC;
El estimado el plan de ejecución es:
Plan parametrizado paralelo estimado
El texto de consulta en la parte superior permanece sin parametrizar mientras que todo lo demás sí lo está. Hay dos marcadores de parámetros ahora, @1
y @2
, porque parametrización simple encontró dos valores literales adecuados.
El real el plan de ejecución sigue el patrón del ejemplo 1:
Plan parametrizado paralelo real
El texto de consulta en la parte superior ahora está parametrizado y la ventana de propiedades contiene valores de parámetros de tiempo de ejecución. Este plan paralelo (con un Ordenar operador) es definitivamente parametrizado por el servidor usando parametrización simple .
Métodos Confiables
Hay razones para todos los comportamientos mostrados hasta ahora, y algunos más. Intentaré explicar muchos de estos en la siguiente parte de esta serie cuando cubra la compilación del plan.
Mientras tanto, la situación con showplan en general y SSMS en particular, es menos que ideal. Es confuso para las personas que han estado trabajando con SQL Server durante toda su carrera. ¿En qué marcadores de parámetros confía y cuáles ignora?
Hay varios métodos confiables para determinar si una declaración en particular tenía una parametrización simple aplicada con éxito o no.
Almacén de consultas
Comenzaré con uno de los más convenientes, el almacén de consultas. Desafortunadamente, no siempre es tan sencillo como podrías imaginar.
Debe habilitar la función de almacenamiento de consultas para el contexto de la base de datos donde se ejecuta la declaración y el OPERATION_MODE
debe establecerse en READ_WRITE
, lo que permite que el almacén de consultas recopile datos de forma activa.
Después de cumplir con estas condiciones, la salida del plan de presentación posterior a la ejecución contiene atributos adicionales, incluido el StatementParameterizationType . Como sugiere el nombre, contiene un código que describe el tipo de parametrización utilizada para la instrucción.
Es visible en la ventana de propiedades de SSMS cuando se selecciona el nodo raíz de un plan:
StatementParameterizationType
Los valores están documentados en sys.query_store_query
:
- 0:ninguno
- 1 – Usuario (parametrización explícita)
- 2 – Parametrización sencilla
- 3 – Parametrización forzada
Este atributo beneficioso solo aparece en SSMS cuando un real se solicita un plan y falta cuando un estimado se selecciona el plano. Es importante recordar que el plan debe estar almacenado en caché . Solicitar un estimado plan de SSMS no almacena en caché el plan producido (desde SQL Server 2012).
Una vez que el plan se almacena en caché, el StatementParameterizationType aparece en los lugares habituales, incluso a través de sys.dm_exec_query_plan
.
También puede confiar en que el tipo de parametrización de otros lugares se registra en el almacén de consultas, como query_parameterization_type_desc
columna en sys.query_store_query
.
Una advertencia importante. Cuando la consulta almacena OPERATION_MODE
está establecido en READ_ONLY
, el StatementParameterizationType el atributo aún se completa en SSMS real planes, pero es siempre cero —dando la falsa impresión de que la declaración no estaba parametrizada cuando bien podría haberlo estado.
Si está feliz de habilitar el almacén de consultas, está seguro de que es de lectura y escritura y solo mira los planes posteriores a la ejecución en SSMS, esto funcionará para usted.
Predicados del Plan Estándar
El texto de consulta que se muestra en la parte superior de la ventana del plan de presentación gráfico en SSMS no es confiable, como se muestra en los ejemplos. Tampoco puede confiar en ParameterList se muestra en las Propiedades ventana cuando se selecciona el nodo raíz del plan. El Texto Parametrizado atributo mostrado para estimado solo planes tampoco es concluyente.
Sin embargo, puede confiar en las propiedades asociadas con operadores de planes individuales. Los ejemplos dados muestran que estos están presentes en la información sobre herramientas al pasar el cursor sobre un operador.
Un predicado que contiene un marcador de parámetro como @1
o @2
indica un plan parametrizado. Los operadores con mayor probabilidad de contener un parámetro son Escaneo de índice , Búsqueda de índice y Filtro .
Predicados con marcadores de parámetro
Si la numeración comienza con @1
, utiliza parametrización simple . La parametrización forzada comienza con @0
. Debo mencionar que el esquema de numeración documentado aquí está sujeto a cambios en cualquier momento:
Advertencia de cambio
Sin embargo, este es el método que uso más a menudo para determinar si un plan estaba sujeto a la parametrización del lado del servidor. Por lo general, es rápido y fácil comprobar visualmente un plan en busca de predicados que contengan marcadores de parámetros. Este método también funciona para ambos tipos de planes, estimado y real .
Objetos de gestión dinámica
Hay varias formas de consultar la memoria caché del plan y las DMO relacionadas para determinar si se parametrizó una declaración. Naturalmente, estas consultas solo funcionan en planes en caché, por lo que la declaración debe haberse ejecutado hasta el final, almacenarse en caché y no desalojarse posteriormente por ningún motivo.
El enfoque más directo es buscar un Adhoc plan utilizando una coincidencia textual SQL exacta con la declaración de interés. El Adhoc el plan será un caparazón que contiene un ParameterizedPlanHandle si la declaración está parametrizada por el servidor. El identificador del plan se usa para ubicar el Preparado plan. Un Adhoc el plan no existirá si la optimización para cargas de trabajo ad hoc está habilitada y la declaración en cuestión solo se ejecutó una vez.
Este tipo de consulta a menudo termina destruyendo una cantidad significativa de XML y escaneando todo el caché del plan al menos una vez. También es fácil equivocarse en el código, sobre todo porque los planes en caché cubren un lote completo. Un lote puede contener varias declaraciones, cada una de las cuales puede estar parametrizada o no. No todas las DMO funcionan con la misma granularidad (lote o estado de cuenta), por lo que es muy fácil despegarse.
A continuación se muestra una forma eficiente de enumerar declaraciones de interés, junto con fragmentos del plan para esas declaraciones individuales:
SELECT StatementText = SUBSTRING(T.[text], 1 + (QS.statement_start_offset / 2), 1 + ((QS.statement_end_offset - QS.statement_start_offset) / 2)), IsParameterized = IIF(T.[text] LIKE N'(%', 'Yes', 'No'), query_plan = TRY_CONVERT(xml, P.query_plan) FROM sys.dm_exec_query_stats AS QS CROSS APPLY sys.dm_exec_sql_text (QS.[sql_handle]) AS T CROSS APPLY sys.dm_exec_text_query_plan ( QS.plan_handle, QS.statement_start_offset, QS.statement_end_offset) AS P WHERE -- Statements of interest T.[text] LIKE N'%DisplayName%Users%' -- Exclude queries like this one AND T.[text] NOT LIKE N'%sys.dm%' ORDER BY QS.last_execution_time ASC, QS.statement_start_offset ASC;
Para ilustrar, ejecutemos un solo lote que contenga los cuatro ejemplos anteriores:
ALTER DATABASE SCOPED CONFIGURATION CLEAR PROCEDURE_CACHE; GO -- Example 1 SELECT U.DisplayName FROM dbo.Users AS U WHERE U.Reputation = 999; -- Example 2 SELECT U.DisplayName, U.CreationDate FROM dbo.Users AS U WHERE U.Reputation = 999; -- Example 3 SELECT U.DisplayName, LOWER(U.DisplayName) FROM dbo.Users AS U WHERE U.Reputation = 999; -- Example 4 SELECT U.DisplayName FROM dbo.Users AS U WHERE U.Reputation >= 5 AND U.DisplayName > N'ZZZ' ORDER BY U.Reputation DESC; GO
El resultado de la consulta DMO es:
Resultado de consulta DMO
Esto confirma que solo los ejemplos 1 y 4 se parametrizaron correctamente.
Contadores de rendimiento
Es posible usar los contadores de rendimiento de SQL Statistics para obtener una visión detallada de la actividad de parametrización tanto para estimado y real planes Los contadores utilizados no tienen alcance por sesión, por lo que deberá utilizar una instancia de prueba sin otra actividad simultánea para obtener resultados precisos.
Voy a complementar la información del contador de parametrización con datos de sys.dm_exec_query_optimizer_info
DMO para proporcionar estadísticas sobre planes triviales también.
Es necesario tener cuidado para evitar que las sentencias que leen la información del contador modifiquen esos contadores. Voy a abordar esto creando un par de procedimientos almacenados temporales:
CREATE PROCEDURE #TrivialPlans AS SET NOCOUNT ON; SELECT OI.[counter], OI.occurrence FROM sys.dm_exec_query_optimizer_info AS OI WHERE OI.[counter] = N'trivial plan'; GO CREATE PROCEDURE #PerfCounters AS SET NOCOUNT ON; SELECT PC.[object_name], PC.counter_name, PC.cntr_value FROM sys.dm_os_performance_counters AS PC WHERE PC.counter_name LIKE N'%Param%';
La secuencia de comandos para probar una declaración en particular se ve así:
ALTER DATABASE SCOPED CONFIGURATION CLEAR PROCEDURE_CACHE; GO EXECUTE #PerfCounters; EXECUTE #TrivialPlans; GO SET SHOWPLAN_XML ON; GO -- The statement(s) under test: -- Example 3 SELECT U.DisplayName, LOWER(U.DisplayName) FROM dbo.Users AS U WHERE U.Reputation = 999; GO SET SHOWPLAN_XML OFF; GO EXECUTE #TrivialPlans; EXECUTE #PerfCounters;
Comenta el SHOWPLAN_XML
procesa por lotes para ejecutar las declaraciones de destino y obtener real planes Déjelos en su lugar para estimado planes de ejecución.
Ejecutar todo como está escrito da los siguientes resultados:
Resultados de la prueba del contador de rendimiento
He resaltado arriba dónde cambiaron los valores al probar el ejemplo 3.
El aumento en el contador del "plan trivial" de 1050 a 1051 muestra que se encontró un plan trivial para la declaración de prueba.
Los contadores de parametrización simple aumentaron en 1 tanto para los intentos como para los errores, lo que muestra que SQL Server intentó parametrizar la declaración, pero falló.
Fin de la Parte 3
En la siguiente parte de esta serie, explicaré las cosas curiosas que hemos visto describiendo cómo la parametrización simple y planes triviales interactuar con el proceso de compilación.
Si cambió su umbral de costo para el paralelismo para ejecutar los ejemplos, recuerde restablecerlo (el mío estaba configurado en 50):
EXECUTE sys.sp_configure @configname = 'cost threshold for parallelism', @configvalue = 50; RECONFIGURE;