Análisis de parámetros vs VARIABLES vs Recompilar vs OPTIMIZAR PARA DESCONOCIDO


40

Así que tuvimos un proceso de larga ejecución que causó problemas esta mañana (30 segundos + tiempo de ejecución). Decidimos verificar para ver si el rastreo de parámetros era el culpable. Por lo tanto, reescribimos el proceso y establecemos los parámetros entrantes en variables para vencer el rastreo de parámetros. Un enfoque probado / verdadero. Bam, tiempo de consulta mejorado (menos de 1 segundo). Al mirar el plan de consulta, las mejoras se encontraron en un índice que el original no estaba usando.

Solo para verificar que no obtuvimos un falso positivo, hicimos un dbcc freeproccache en el proceso original y volvimos a analizar para ver si los resultados mejorados serían los mismos. Pero, para nuestra sorpresa, el proceso original seguía siendo lento. Intentamos nuevamente con un WITH RECOMPILE, aún lento (probamos una recompilación en la llamada al proceso y dentro del proceso en sí). Incluso reiniciamos el servidor (cuadro de desarrollo obviamente).

Entonces, mi pregunta es esta ... ¿cómo puede ser la culpa el rastreo de parámetros cuando obtenemos la misma consulta lenta en un caché de plan vacío ... no debería haber ningún parámetro para rastrear?

¿Nos están afectando las estadísticas de la tabla no relacionadas con el caché del plan? Y si es así, ¿por qué ayudaría establecer los parámetros entrantes a las variables?

En la prueba adicional también se encontró que la inserción de la OPCIÓN (optimizar para DESCONOCIDO) en la parte interna del proc DID obtener el plan de mejora esperada.

Entonces, algunos de ustedes, personas más inteligentes que yo, ¿pueden darnos algunas pistas sobre lo que sucede detrás de escena para producir este tipo de resultado?

En otra nota, el plan lento también se aborta temprano con razón, GoodEnoughPlanFoundmientras que el plan rápido no tiene una razón de aborto temprano en el plan real.

En resumen

  • Crear variables a partir de parámetros entrantes (1 segundo)
  • con recompilación (más de 30 segundos)
  • dbcc freeproccache (más de 30 segundos)
  • OPCIÓN (OPTIMIZAR PARA UKNOWN) (1 seg.)

ACTUALIZAR:

Vea el plan de ejecución lenta aquí: https://www.dropbox.com/s/cmx2lrsea8q8mr6/plan_slow.xml

Vea el plan de ejecución rápida aquí: https://www.dropbox.com/s/b28x6a01w7dxsed/plan_fast.xml

Nota: tabla, esquema, nombres de objetos cambiados por razones de seguridad.

Respuestas:


43

La consulta es

SELECT SUM(Amount) AS SummaryTotal
FROM   PDetail WITH(NOLOCK)
WHERE  ClientID = @merchid
       AND PostedDate BETWEEN @datebegin AND @dateend 

La tabla contiene 103,129,000 filas.

El plan rápido busca ClientId con un predicado residual en la fecha, pero necesita realizar 96 búsquedas para recuperar el Amount. La <ParameterList>sección del plan es la siguiente.

        <ParameterList>
          <ColumnReference Column="@dateend" 
                           ParameterRuntimeValue="'2013-02-01 23:59:00.000'" />
          <ColumnReference Column="@datebegin" 
                           ParameterRuntimeValue="'2013-01-01 00:00:00.000'" />
          <ColumnReference Column="@merchid" 
                           ParameterRuntimeValue="(78155)" />
        </ParameterList>

El plan lento busca por fecha y tiene búsquedas para evaluar el predicado residual en ClientId y recuperar la cantidad (Estimado 1 vs 7,388,383 real). La <ParameterList>sección es

        <ParameterList>
          <ColumnReference Column="@EndDate" 
                           ParameterCompiledValue="'2013-02-01 23:59:00.000'" 
                           ParameterRuntimeValue="'2013-02-01 23:59:00.000'" />
          <ColumnReference Column="@BeginDate" 
                           ParameterCompiledValue="'2013-01-01 00:00:00.000'"               
                           ParameterRuntimeValue="'2013-01-01 00:00:00.000'" />
          <ColumnReference Column="@ClientID" 
                           ParameterCompiledValue="(78155)" 
                           ParameterRuntimeValue="(78155)" />
        </ParameterList>

En este segundo caso, el noParameterCompiledValue está vacío. SQL Server olfateó con éxito los valores utilizados en la consulta.

El libro "Solución de problemas prácticos de SQL Server 2005" tiene esto que decir sobre el uso de variables locales

El uso de variables locales para vencer el rastreo de parámetros es un truco bastante común, pero las sugerencias OPTION (RECOMPILE)y OPTION (OPTIMIZE FOR)... generalmente son soluciones más elegantes y ligeramente menos riesgosas


Nota

En SQL Server 2005, la compilación de nivel de instrucción permite que la compilación de una instrucción individual en un procedimiento almacenado se difiera hasta justo antes de la primera ejecución de la consulta. Para entonces, se conocería el valor de la variable local. Teóricamente, SQL Server podría aprovechar esto para detectar los valores de las variables locales de la misma manera que los parámetros. Sin embargo, debido a que era común usar variables locales para vencer el rastreo de parámetros en SQL Server 7.0 y SQL Server 2000+, el rastreo de variables locales no estaba habilitado en SQL Server 2005. Sin embargo, puede habilitarse en una versión futura de SQL Server, lo cual es una buena opción. razón para usar una de las otras opciones descritas en este capítulo si tiene una opción.


A partir de una prueba rápida de este fin, el comportamiento descrito anteriormente sigue siendo el mismo en 2008 y 2012 y las variables no se analizan para la compilación diferida, sino solo cuando OPTION RECOMPILEse utiliza una pista explícita .

DECLARE @N INT = 0

CREATE TABLE #T ( I INT );

/*Reference to #T means this statement is subject to deferred compile*/
SELECT *
FROM   master..spt_values
WHERE  number = @N
       AND EXISTS(SELECT COUNT(*) FROM #T)

SELECT *
FROM   master..spt_values
WHERE  number = @N
OPTION (RECOMPILE)

DROP TABLE #T 

A pesar de la compilación diferida, la variable no se detecta y el recuento de filas estimado es inexacto

Estimaciones vs reales

Así que supongo que el plan lento se relaciona con una versión parametrizada de la consulta.

El valor ParameterCompiledValuees igual a ParameterRuntimeValuepara todos los parámetros, por lo que este no es un análisis típico de parámetros (donde el plan se compiló para un conjunto de valores y luego se ejecutó para otro conjunto de valores).

El problema es que el plan que se compila para los valores correctos de los parámetros es inapropiado.

Es probable que tenga problemas con las fechas ascendentes descritas aquí y aquí . Para una tabla con 100 millones de filas, debe insertar (o modificar) 20 millones antes de que SQL Server actualice automáticamente las estadísticas por usted. Parece que la última vez que se actualizaron, las filas cero coincidieron con el intervalo de fechas en la consulta, pero ahora 7 millones sí.

Puede programar actualizaciones de estadísticas más frecuentes, considerar marcas de seguimiento 2389 - 90o usarlas OPTIMIZE FOR UKNOWNpara que simplemente recurra a conjeturas en lugar de poder usar las estadísticas engañosas actualmente en la datetimecolumna.

Esto podría no ser necesario en la próxima versión de SQL Server (después de 2012). Un elemento relacionado de Connect contiene la respuesta intrigante

Publicado por Microsoft el 8/28/2012 a las 1:35 p. M.
Hemos realizado una mejora en la estimación de la cardinalidad para la próxima versión principal que esencialmente soluciona esto. Estén atentos para más detalles una vez que salgan nuestros avances. Eric

Benjamin Nevarez analiza esta mejora de 2014 hacia el final del artículo:

Una primera mirada al nuevo estimador de cardinalidad de SQL Server .

Parece que el nuevo estimador de cardinalidad retrocederá y usará la densidad promedio en este caso en lugar de dar la estimación de 1 fila.

Algunos detalles adicionales sobre el estimador de cardinalidad de 2014 y el problema clave ascendente aquí:

Nueva funcionalidad en SQL Server 2014 - Parte 2 - Nueva estimación de cardinalidad


29

Entonces, mi pregunta es esta ... ¿cómo puede ser la culpa la detección de parámetros cuando obtenemos la misma consulta lenta en un caché de plan vacío ... no debería haber ningún parámetro para detectar?

Cuando SQL Server compila una consulta que contiene valores de parámetros, detecta los valores específicos de esos parámetros para la estimación de cardinalidad (recuento de filas). En su caso, los valores particulares de @BeginDate, @EndDatey @ClientIDse utilizan al elegir un plan de ejecución. Puede encontrar más detalles sobre la detección de parámetros aquí y aquí . Proporciono estos enlaces de fondo porque la pregunta anterior me hace pensar que el concepto se entiende de manera imperfecta en la actualidad: siempre hay valores de parámetros para rastrear cuando se compila un plan.

De todos modos, eso no viene al caso, porque la detección de parámetros no es el problema aquí como Martin Smith ha señalado. En el momento en que se compiló la consulta lenta, las estadísticas indicaron que no había filas para los valores olfateados de @BeginDatey @EndDate:

Plan lento olfateo de valores

Los valores olfateados son muy recientes, lo que sugiere el problema clave ascendente que Martin menciona. Debido a que se estima que la búsqueda de índice en las fechas devuelve solo una fila, el optimizador elige un plan que empuja el predicado ClientIDal operador de búsqueda de claves como un residuo.

La estimación de una sola fila también es la razón por la que el optimizador deja de buscar mejores planes y devuelve un mensaje de Se encontró un plan suficientemente bueno. El costo total estimado del plan lento con la estimación de una sola fila es de solo 0.013136 unidades de costo, por lo que no tiene sentido tratar de encontrar algo mejor. Excepto, por supuesto, que la búsqueda en realidad devuelve 7.388.383 filas en lugar de una, lo que provoca el mismo número de búsquedas clave.

Las estadísticas pueden ser difíciles de mantener actualizadas y útiles en tablas grandes y la partición presenta desafíos propios en ese sentido. Yo no he tenido un éxito particular con las marcas de seguimiento 2389 y 2390, pero puede probarlas. Las versiones más recientes de SQL Server (R2 SP1 y posterior) tienen actualizaciones de estadísticas dinámicas disponibles, pero estas actualizaciones de estadísticas por partición aún no se implementan. Mientras tanto, es posible que desee programar una actualización manual de estadísticas cada vez que realice cambios significativos en esta tabla.

Para esta consulta en particular, pensaría en implementar el índice sugerido por el optimizador durante la compilación del plan de consulta rápida:

/*
The Query Processor estimates that implementing the following index could improve
the query cost by 98.8091%.

WARNING: This is only an estimate, and the Query Processor is making this 
recommendation based solely upon analysis of this specific query.
It has not considered the resulting index size, or its workload-wide impact,
including its impact on INSERT, UPDATE, DELETE performance.
These factors should be taken into account before creating this index.
*/
CREATE NONCLUSTERED INDEX [<Name of Missing Index>]
ON [dbo].[PDetail] ([ClientID],[PostedDate])
INCLUDE ([Amount]);

El índice debe estar alineado con particiones, con una ON PartitionSchemeName (PostedDate)cláusula, pero el punto es que proporcionar una ruta de acceso a datos obviamente mejor ayudará al optimizador a evitar malas elecciones de plan, sin recurrir a OPTIMIZE FOR UNKNOWNsugerencias o soluciones anticuadas como el uso de variables locales.

Con el índice mejorado, Amountse eliminará la Búsqueda de claves para recuperar la columna, el procesador de consultas aún puede realizar la eliminación de la partición dinámica y utilizar una búsqueda para encontrar el ClientIDrango particular y el intervalo de fechas.


Ojalá pudiera marcar dos respuestas como correctas, pero nuevamente, gracias por la información adicional, muy instructiva.
RThomas

1
Han pasado un par de años desde que publiqué esto ... pero solo quería decírtelo. Todavía uso el término "imperfectamente entendido" todo el maldito tiempo, y siempre pienso en Paul White cuando lo hago. Me hace reír cada vez.
RThomas

0

Yo tenía el mismo problema en un procedimiento almacenado se convirtió lento, y OPTIMIZE FOR UNKNOWNy RECOMPILEsugerencias de consulta resuelto la lentitud y aceleró el tiempo de ejecución. Sin embargo, los siguientes dos métodos no afectaron la lentitud del procedimiento almacenado: (i) Borrar el caché (ii) usando WITH RECOMPILE. Entonces, tal como dijiste, en realidad no se trataba de detectar parámetros.

Las banderas de seguimiento 2389 y 2390 tampoco ayudaron. Solo actualizar las estadísticas ( EXEC sp_updatestats) lo hizo por mí.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.