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

Múltiples Planes para una Consulta Idéntica

A menudo veo personas que luchan con SQL Server cuando ven dos planes de ejecución diferentes para lo que creen que es la misma consulta. Por lo general, esto se descubre después de otras observaciones, como tiempos de ejecución muy diferentes. Digo que creen que es la misma consulta porque a veces lo es y a veces no.

Uno de los casos más comunes es cuando están probando una consulta en SSMS y obtienen un plan diferente al que obtienen de su aplicación. Hay potencialmente dos factores en juego aquí (que también podrían ser relevantes cuando la comparación NO es entre la aplicación y SSMS):

  1. La aplicación casi siempre tiene diferentes SET configuración que SSMS (son cosas como ARITHABORT , ANSI_NULLS y QUOTED_IDENTIFIER ). Esto obliga a SQL Server a almacenar los dos planes por separado; Erland Sommarskog ha tratado esto con gran detalle en su artículo, ¿Lento en la aplicación, rápido en SSMS?
  2. Los parámetros utilizados por la aplicación cuando se compiló por primera vez su copia del plan podrían haber sido muy diferentes y haber dado lugar a un plan diferente de los utilizados la primera vez que se ejecutó la consulta desde SSMS; esto se conoce como detección de parámetros. . Erland también habla de eso en profundidad, y no voy a regurgitar sus recomendaciones, pero resumiré recordándoles que probar la consulta de la aplicación en SSMS no siempre es útil, ya que es muy poco probable que sea una prueba de manzanas con manzanas.

Hay un par de otros escenarios que son un poco más oscuros que menciono en mi charla sobre malos hábitos y mejores prácticas. Estos son casos en los que los planes no son diferentes, pero hay varias copias del mismo plan que inflan la memoria caché del plan. Pensé que debería mencionarlos aquí porque siempre sorprenden a muchas personas.

Las mayúsculas y minúsculas y los espacios en blanco son importantes

SQL Server convierte el texto de la consulta en un formato binario, lo que significa que cada carácter del texto de la consulta es crucial. Tomemos las siguientes consultas simples:

USE AdventureWorks2014;
DBCC FREEPROCCACHE WITH NO_INFOMSGS;
GO
SELECT StoreID FROM Sales.Customer;
GO -- original query
GO
SELECT  StoreID FROM Sales.Customer;
GO ----^---- extra space
GO
SELECT storeid FROM sales.customer;
GO ---- lower case names
GO
select StoreID from Sales.Customer;
GO ---- lower case keywords
GO

Estos generan exactamente los mismos resultados, obviamente, y generan exactamente el mismo plan. Sin embargo, si miramos lo que tenemos en la caché del plan:

SELECT t.[text], p.size_in_bytes, p.usecounts
 FROM sys.dm_exec_cached_plans AS p
 CROSS APPLY sys.dm_exec_sql_text(p.plan_handle) AS t
 WHERE LOWER(t.[text]) LIKE N'%sales'+'.'+'customer%';

Los resultados son desafortunados:

Entonces, en este caso, está claro que las mayúsculas y minúsculas y los espacios en blanco son muy importantes. Hablé de esto con mucho más detalle en mayo pasado.

Las referencias de esquema son importantes

Anteriormente escribí en un blog sobre la importancia de especificar el prefijo del esquema al hacer referencia a cualquier objeto, pero en ese momento no estaba completamente consciente de que también tenía implicaciones en la memoria caché del plan.

Echemos un vistazo a un caso muy simple en el que tenemos dos usuarios con diferentes esquemas predeterminados, y ejecutan exactamente el mismo texto de consulta, sin poder hacer referencia al objeto por su esquema:

USE AdventureWorks2014;
DBCC FREEPROCCACHE WITH NO_INFOMSGS;
GO
 
CREATE USER SQLPerf1 WITHOUT LOGIN WITH DEFAULT_SCHEMA = Sales;
CREATE USER SQLPerf2 WITHOUT LOGIN WITH DEFAULT_SCHEMA = Person;
GO
 
CREATE TABLE dbo.AnErrorLog(id INT);
GRANT SELECT ON dbo.AnErrorLog TO SQLPerf1, SQLPerf2;
GO
 
EXECUTE AS USER = N'SQLPerf1';
GO
SELECT id FROM AnErrorLog;
GO
REVERT;
GO
EXECUTE AS USER = N'SQLPerf2';
GO
SELECT id FROM AnErrorLog;
GO
REVERT;
GO

Ahora, si echamos un vistazo a la memoria caché del plan, podemos extraer sys.dm_exec_plan_attributes para ver exactamente por qué tenemos dos planes diferentes para consultas idénticas:

SELECT t.[text], p.size_in_bytes, p.usecounts, 
  [schema_id] = pa.value, 
  [schema] = s.name
FROM sys.dm_exec_cached_plans AS p
CROSS APPLY sys.dm_exec_sql_text(p.plan_handle) AS t
CROSS APPLY sys.dm_exec_plan_attributes(p.plan_handle) AS pa
INNER JOIN sys.schemas AS s ON s.[schema_id] = pa.value
WHERE t.[text] LIKE N'%AnError'+'Log%' 
AND pa.attribute = N'user_id';

Resultados:

Y si lo ejecuta todo de nuevo pero agrega el dbo. prefijo a ambas consultas, verá que solo hay un plan que se usa dos veces. Esto se convierte en un argumento muy convincente para hacer referencia siempre completa a los objetos.

Configuración reducida de SET

Como nota al margen, puede utilizar un enfoque similar para determinar si SET la configuración es diferente para dos o más versiones de la misma consulta. En este caso, estamos investigando las consultas involucradas con múltiples planes generados por diferentes llamadas al mismo procedimiento almacenado, pero también podría identificarlas por el texto de consulta o el hash de consulta.

SELECT p.plan_handle, p.usecounts, p.size_in_bytes, 
  set_options = MAX(a.value)
FROM sys.dm_exec_cached_plans AS p
CROSS APPLY sys.dm_exec_sql_text(p.plan_handle) AS t
CROSS APPLY sys.dm_exec_plan_attributes(p.plan_handle) AS a
WHERE t.objectid = OBJECT_ID(N'dbo.procedure_name')
AND a.attribute = N'set_options'
GROUP BY p.plan_handle, p.usecounts, p.size_in_bytes;

Si tiene varios resultados aquí, debería ver diferentes valores para set_options (que es una máscara de bits). Eso es solo el comienzo; Voy a salir aquí y decirle que puede determinar qué conjunto de opciones están habilitados para cada plan desempaquetando el valor de acuerdo con la sección "Evaluación de opciones de conjunto" aquí. Sí, soy así de perezoso.

Conclusión

Hay varias razones por las que puede ver diferentes planes para la misma consulta (o lo que cree que es la misma consulta). En la mayoría de los casos, puede aislar la causa con bastante facilidad; el desafío es a menudo saber buscarlo en primer lugar. En mi próxima publicación, hablaré sobre un tema ligeramente diferente:por qué una base de datos restaurada en un servidor "idéntico" podría generar diferentes planes para la misma consulta.