OPTION (RECOMPILE) es siempre más rápido; ¿Por qué?

169

Encontré una situación extraña en la que agregar OPTION (RECOMPILE)mi consulta hace que se ejecute en medio segundo, mientras que la omisión hace que la consulta tarde más de cinco minutos.

Este es el caso cuando la consulta se ejecuta desde el Analizador de consultas o desde mi programa C # a través de SqlCommand.ExecuteReader(). Llamar (o no llamar) DBCC FREEPROCCACHEo DBCC dropcleanbuffersno hace ninguna diferencia; Los resultados de la consulta siempre se devuelven instantáneamente con OPTION (RECOMPILE)y más de cinco minutos sin ella. La consulta siempre se llama con los mismos parámetros [por el bien de esta prueba].

Estoy usando SQL Server 2008.

Me siento bastante cómodo escribiendo SQL, pero nunca antes he usado un OPTIONcomando en una consulta y no estaba familiarizado con todo el concepto de cachés de planes hasta escanear las publicaciones en este foro. Mi comprensión de las publicaciones es que OPTION (RECOMPILE)es una operación costosa. Aparentemente crea una nueva estrategia de búsqueda para la consulta. Entonces, ¿por qué es que las consultas posteriores que omiten OPTION (RECOMPILE)son tan lentas? ¿No deberían las consultas posteriores hacer uso de la estrategia de búsqueda que se calculó en la llamada anterior que incluía la sugerencia de recompilación?

¿Es muy inusual tener una consulta que requiera una sugerencia de recompilación en cada llamada?

Perdón por la pregunta de nivel de entrada, pero realmente no puedo hacer cara o cruz de esto.

ACTUALIZACIÓN: me han pedido que publique la consulta ...

select acctNo,min(date) earliestDate 
from( 
    select acctNo,tradeDate as date 
    from datafeed_trans 
    where feedid=@feedID and feedDate=@feedDate 

    union 

    select acctNo,feedDate as date 
    from datafeed_money 
    where feedid=@feedID and feedDate=@feedDate 

    union 

    select acctNo,feedDate as date 
    from datafeed_jnl 
    where feedid=@feedID and feedDate=@feedDate 
)t1 
group by t1.acctNo
OPTION(RECOMPILE)

Cuando ejecuto la prueba desde el Analizador de consultas, antepongo las siguientes líneas:

declare @feedID int
select @feedID=20

declare @feedDate datetime
select @feedDate='1/2/2009'

Al llamarlo desde mi programa C #, los parámetros se pasan a través de la SqlCommand.Parameterspropiedad.

Para los fines de esta discusión, puede suponer que los parámetros nunca cambian, por lo que podemos descartar el olor de parámetros subóptimos como la causa.

Chad Decker
fuente
3
¿Cuáles son los parámetros para la consulta? Mira este artículo. blogs.msdn.com/b/turgays/archive/2013/09/10/… Básicamente, SQL intenta generar el plan de consulta en función de los parámetros cuando el proceso se compila por primera vez. Puede generar un plan que no es óptimo cuando comienzas a pasar parámetros diferentes, posiblemente más realistas
Sparky el
3
¿La consulta es lo suficientemente concisa como para enumerarla aquí? Creo que Sparky es correcto y probablemente esté relacionado con la detección de parámetros, tuve un problema similar que me confundió hasta que leí este excelente artículo: sommarskog.se/query-plan-mysteries.html
Chris
1
Pero en este caso (por el bien de esta prueba) siempre estoy pasando los mismos parámetros. Ninguna otra aplicación pudo colarse y llamar a la consulta usando otros parámetros. Gracias por los articulos. Será revisada, será reseñada.
Chad Decker
2
Esto puede suceder ya sea porque huele los valores de los parámetros y variables o porque hace mayores simplificaciones. Los ejemplos de las simplificaciones mayores serían colapsando X = @X OR @X IS NULLa X=@Xy realizar una buscan vemos aquí o empujar hacia abajo los predicados más contra una vista con las funciones de ventana
Martin Smith
3
Después de editar, el ejemplo del Analizador de consultas usa variables, no parámetros. el valor de esos nunca se olfatea excepto con RECOMPILE. En cualquier caso, capture los planes de ejecución y observe las diferencias.
Martin Smith

Respuestas:

157

Hay momentos en que usar OPTION(RECOMPILE)tiene sentido. En mi experiencia, la única vez que esta es una opción viable es cuando está utilizando SQL dinámico. Antes de explorar si esto tiene sentido en su situación, recomendaría reconstruir sus estadísticas. Esto se puede hacer ejecutando lo siguiente:

EXEC sp_updatestats

Y luego recrear su plan de ejecución. Esto asegurará que cuando se cree su plan de ejecución, utilizará la información más reciente.

Agregar OPTION(RECOMPILE)reconstruye el plan de ejecución cada vez que se ejecuta su consulta. Nunca he escuchado eso descrito, creates a new lookup strategypero tal vez solo estamos usando términos diferentes para la misma cosa.

Cuando se crea un procedimiento almacenado (sospecho que está llamando a sql ad-hoc desde .NET, pero si está utilizando una consulta parametrizada, esto termina siendo una llamada de proceso almacenado ) SQL Server intenta determinar el plan de ejecución más efectivo para esta consulta basado en los datos de su base de datos y los parámetros pasados ​​( análisis de parámetros ), y luego almacena en caché este plan. Esto significa que si crea la consulta donde hay 10 registros en su base de datos y luego la ejecuta cuando hay 100,000,000 registros, el plan de ejecución en caché ya no será el más efectivo.

En resumen: no veo ningún motivo que OPTION(RECOMPILE)sea ​​beneficioso aquí. Sospecho que solo necesita actualizar sus estadísticas y su plan de ejecución. La reconstrucción de estadísticas puede ser una parte esencial del trabajo de DBA dependiendo de su situación. Si aún tiene problemas después de actualizar sus estadísticas, sugeriría publicar ambos planes de ejecución.

Y para responder a su pregunta, sí, diría que es muy inusual que su mejor opción sea volver a compilar el plan de ejecución cada vez que ejecute la consulta.

Abe Miessler
fuente
22
Sí, sp_updatestats hizo el truco. Usted dio en el clavo cuando mencionó que una consulta se ejecutó inicialmente en una tabla con 10 registros, y ahora la tabla tiene millones de registros. Ese fue mi caso exactamente. No lo mencioné en la publicación porque no pensé que importara. Cosas fascinantes Gracias de nuevo.
Chad Decker
3
Es la única forma en que encontré trabajar con variables de tabla, porque SQL siempre piensa que hay una sola fila. Cuando contiene varios miles de filas, se convierte en un problema.
Alex Zhukovskiy
44
Un detalle interesante: la actualización de estadísticas invalida implícitamente todos los planes en caché que usan estas estadísticas, pero solo si las estadísticas realmente cambiaron después de la acción de actualización . Entonces, para tablas de solo lectura altamente sesgadas, parece que explícito OPTION (RECOMPILE)podría ser la única solución.
Groo
141

A menudo, cuando hay una diferencia drástica de una consulta a otra, encuentro que a menudo es uno de los 5 problemas.

  1. ESTADÍSTICAS- Las estadísticas están desactualizadas. Una base de datos almacena estadísticas sobre el rango y la distribución de los tipos de valores en varias columnas en tablas e índices. Esto ayuda al motor de consulta a desarrollar un "Plan" de ataque sobre cómo realizará la consulta, por ejemplo, el tipo de método que usará para unir claves entre tablas usando un hash o mirando a través de todo el conjunto. Puede llamar a Estadísticas de actualización en toda la base de datos o solo en ciertas tablas o índices. Esto ralentiza la consulta de una ejecución a otra porque cuando las estadísticas están desactualizadas, es probable que el plan de consulta no sea óptimo para los datos recién insertados o modificados para la misma consulta (explicado más adelante). Es posible que no sea adecuado actualizar las estadísticas inmediatamente en una base de datos de producción, ya que habrá algo de sobrecarga, desaceleración y retraso dependiendo de la cantidad de datos a muestrear. También puede optar por utilizar un Análisis completo o Muestreo para actualizar las Estadísticas. Si observa el Plan de consulta, también puede ver las estadísticas de los Índices en uso, como mediante el comandoDBCC SHOW_STATISTICS (nombre de tabla, nombre de índice) . Esto le mostrará la distribución y los rangos de las claves que utiliza el plan de consulta para basar su enfoque.

  2. RASTREO DE PARÁMETROS : el plan de consulta que se almacena en caché no es óptimo para los parámetros particulares que está pasando, a pesar de que la consulta en sí no ha cambiado. Por ejemplo, si pasa un parámetro que solo recupera 10 de 1,000,000 de filas, entonces el plan de consulta creado puede usar un Hash Join, sin embargo, si el parámetro que pasa usará 750,000 de las 1,000,000 filas, el plan creado puede ser un exploración de índice o exploración de tabla. En tal situación, puede decirle a la instrucción SQL que use la opción OPCIÓN (RECOMPILAR) o un SP para usar CON RECOMPILAR. Para decirle al motor que se trata de un "Plan de uso único" y no utilizar un Plan en caché que probablemente no se aplique. No existe una regla sobre cómo tomar esta decisión, depende de saber la forma en que los usuarios utilizarán la consulta.

  3. ÍNDICES : es posible que la consulta no haya cambiado, pero un cambio en otro lugar, como la eliminación de un índice muy útil, ha ralentizado la consulta.

  4. FILAS CAMBIADAS : las filas que está consultando cambian drásticamente de llamada a llamada. Por lo general, las estadísticas se actualizan automáticamente en estos casos. Sin embargo, si está creando SQL dinámico o llamando a SQL dentro de un ciclo cerrado, existe la posibilidad de que esté utilizando un plan de consulta obsoleto basado en el número drástico incorrecto de filas o estadísticas. Nuevamente, en este caso, la OPCIÓN (RECOMPILAR) es útil.

  5. LA LÓGICA Es la lógica, su consulta ya no es eficiente, estaba bien para un pequeño número de filas, pero ya no se escala. Esto generalmente implica un análisis más profundo del Plan de consulta. Por ejemplo, ya no puede hacer cosas a granel, sino que tiene que dividir las cosas y hacer commits más pequeños, o su producto cruzado estaba bien para un conjunto más pequeño pero ahora ocupa CPU y memoria a medida que se amplía, esto también puede ser cierto usando DISTINCT, está llamando a una función para cada fila, sus coincidencias de teclas no usan un índice debido a la conversión de tipo CASTING o NULLS o funciones ... Demasiadas posibilidades aquí.

En general, cuando escribe una consulta, debe tener una idea mental de cómo se distribuyen ciertos datos dentro de su tabla. Una columna, por ejemplo, puede tener un número uniformemente distribuido de valores diferentes, o puede estar sesgada, el 80% del tiempo tiene un conjunto específico de valores, ya sea que la distribución varíe con frecuencia con el tiempo o sea bastante estática. Esto le dará una mejor idea de cómo crear una consulta eficiente. Pero también, al depurar el rendimiento de la consulta, se tiene una base para construir una hipótesis de por qué es lenta o ineficiente.

CodeCowboyOrg
fuente
2
gracias amigo. Esta es una excelente información. No habría podido entender su respuesta cuando originalmente publiqué mi pregunta, pero ahora tiene mucho sentido para mí.
Chad Decker
3
RANURAR PARÁMETROS es, con mucho, el mayor obstáculo para mi existencia. Ni siquiera sabía sobre este comando hasta una pregunta fallida de la entrevista. Mi solución para la detección de parámetros siempre ha sido hacer hash de los valores de los parámetros y agregar "AND {hash} = {hash}" para que el sql siempre sea diferente para diferentes valores. Un truco, pero funcionó.
Jeremy Boyd
27

Para agregar a la excelente lista (dada por @CodeCowboyOrg) de situaciones en las que OPTION (RECOMPILE) puede ser muy útil,

  1. Tabla de variables . Cuando utiliza variables de tabla, no habrá estadísticas preconstruidas para la variable de tabla, lo que a menudo genera grandes diferencias entre las filas estimadas y reales en el plan de consulta. El uso de OPTION (RECOMPILE) en consultas con variables de tabla permite la generación de un plan de consulta que tiene una estimación mucho mejor de los números de fila involucrados. Tuve un uso particularmente crítico de una variable de tabla que era inutilizable, y que iba a abandonar, hasta que agregué OPTION (RECOMPILE). El tiempo de ejecución pasó de horas a solo unos minutos. Probablemente sea inusual, pero en cualquier caso, si está utilizando variables de tabla y está trabajando en la optimización, vale la pena ver si OPTION (RECOMPILE) hace la diferencia.
DWright
fuente
1
Tengo una consulta con 5 variables de tabla. En mi máquina se ejecuta durante más de media hora. En la máquina de mi compañero de trabajo se ejecuta en <1 segundo. Las máquinas tienen hardware similar y la misma versión de SQL Server. Si ambos agregamos OPTION (RECOMPILE), entonces se ejecuta en 2 segundos en ambas máquinas. En todos los casos, la prueba de ejecución se lleva a cabo en SSMS. ¿Qué podría estar causando esta diferencia?
Adam
1
¿Puede comparar el plan de ejecución para él en su máquina y en la máquina de sus colegas sin Opción (recompilar)? Eso podría mostrar la fuente de la diferencia.
DWright
1
para tablas temporales, ¿es la misma situación?
Muflix
1
@muflix: buena pregunta. No creo que el efecto sea el mismo para las tablas temporales, ya que tienen estadísticas y el motor debería tomar decisiones automáticas de recompilación como para otras tablas, creo (pero no estoy seguro). Quizás alguien más lo sabe con mayor certeza.
DWright
2
Las estadísticas en las tablas temporales no se actualizan ni se vuelven a compilar automáticamente, por lo que el programador debe hacerlo.
J. Michael Wuerth
1

Las primeras acciones antes de ajustar las consultas son desfragmentar / reconstruir los índices y las estadísticas, de lo contrario está perdiendo el tiempo.

Debe verificar el plan de ejecución para ver si es estable (es el mismo cuando cambia los parámetros), de lo contrario, es posible que deba crear un índice de cobertura (en este caso para cada tabla) (sabiendo que el sistema puede crear uno que es útil para otras consultas también).

como ejemplo: crear índice idx01_datafeed_trans en datafeed_trans (feedid, feedDate) INCLUDE (acctNo, tradeDate)

si el plan es estable o puede estabilizarlo, puede ejecutar la oración con sp_executesql ('oración SQL') para guardar y usar un plan de ejecución fijo.

si el plan es inestable, debe usar una declaración ad-hoc o EXEC ('oración SQL') para evaluar y crear un plan de ejecución cada vez. (o un procedimiento almacenado "con recompilación").

Espero eso ayude.

Cristian Solervicéns
fuente
1

Necroing esta pregunta, pero hay una explicación que nadie parece haber considerado.

ESTADÍSTICAS: las estadísticas no están disponibles o son engañosas

Si todo lo siguiente es cierto:

  1. Es probable que las columnas feedid y feedDate estén altamente correlacionadas (por ejemplo, un id de feed es más específico que una fecha de feed y el parámetro de fecha es información redundante).
  2. No hay índice con ambas columnas como columnas secuenciales.
  3. No hay estadísticas creadas manualmente que cubran ambas columnas.

Entonces, el servidor sql puede estar asumiendo incorrectamente que las columnas no están correlacionadas, lo que lleva a estimaciones de cardinalidad más bajas de lo esperado para aplicar ambas restricciones y un plan de ejecución deficiente seleccionado. La solución en este caso sería crear un objeto de estadísticas que vincule las dos columnas, lo cual no es una operación costosa.

MonoPushBotón
fuente