Consulta T-SQL usando un plan completamente diferente dependiendo del número de filas que estoy actualizando

20

Tengo una instrucción SQL UPDATE con una cláusula "TOP (X)", y la fila en la que estoy actualizando los valores tiene aproximadamente 4 mil millones de filas. Cuando uso "TOP (10)", obtengo un plan de ejecución que se ejecuta casi instantáneamente, pero cuando uso "TOP (50)" o más grande, la consulta nunca (al menos, no mientras espero) finaliza, y Utiliza un plan de ejecución completamente diferente. La consulta más pequeña usa un plan muy simple con un par de búsquedas de índice y una unión de bucle anidado, donde la misma consulta exacta (con un número diferente de filas en la cláusula TOP de la instrucción UPDATE) usa un plan que involucra dos búsquedas de índice diferentes , un carrete de mesa, paralelismo y un montón de otras complejidades.

He usado "OPTION (USE PLAN ...)" para forzarlo a usar el plan de ejecución generado por la consulta más pequeña; cuando hago esto, puedo actualizar hasta 100,000 filas en unos segundos. Sé que el plan de consulta es bueno, pero SQL Server solo elegirá ese plan por sí solo cuando solo intervenga un pequeño número de filas; cualquier recuento de filas decente en mi actualización dará como resultado un plan subóptimo.

Pensé que el paralelismo podría ser el culpable, así que puse MAXDOP 1la consulta, pero sin ningún efecto: ese paso se ha ido, pero la mala elección / rendimiento no lo es. También corrí sp_updatestatsesta mañana para asegurarme de que esa no fuera la causa.

He adjuntado los dos planes de ejecución: el más corto también es el más rápido. Además, aquí está la consulta en cuestión (vale la pena señalar que el SELECT que he incluido parece ser rápido en los casos de recuento de filas grandes y pequeñas):

    update top (10000) FactSubscriberUsage3
               set AccountID = sma.CustomerID
    --select top 50 f.AccountID, sma.CustomerID
      from FactSubscriberUsage3 f
      join dimTime t
        on f.TimeID = t.TimeID
      join #mac sma
        on f.macid = sma.macid
       and t.TimeValue between sma.StartDate and sma.enddate 
     where f.AccountID = 0 --There's a filtered index on the table for this

Aquí está el plan rápido : Plan de ejecución rápida

Y aquí está el más lento : Plan de ejecución lenta

¿Hay algo obvio en la forma en que estoy configurando mi consulta o en el plan de ejecución siempre que se preste a la mala elección que está haciendo el motor de consulta? Si es necesario, también puedo incluir las definiciones de tabla involucradas y los índices que se definen en ellas.

Para aquellos que pidieron una versión de estadísticas de los objetos de la base de datos: ni siquiera me di cuenta de que podían hacer eso, ¡pero tiene mucho sentido! Intenté generar los scripts para una base de datos solo de estadísticas para que otros pudieran probar los planes de ejecución por sí mismos, pero puedo generar generar estadísticas / histogramas en mi índice filtrado (error de sintaxis en el script, parece), así que estoy sin suerte allí. Traté de eliminar el filtro y los planes de consulta estaban cerca, pero no exactamente lo mismo, y no quiero enviar a nadie en una persecución.

Actualización y algunos planes de ejecución más completos: en primer lugar, el Explorador de planes de SQL Sentry es una herramienta increíble. Ni siquiera sabía que existía hasta que vi las otras preguntas del plan de consulta en este sitio, y tenía bastante que decir sobre cómo se estaban ejecutando mis consultas. Aunque no estoy seguro de cómo abordar el problema, dejaron en claro cuál es el problema.

Aquí está el resumen de 10, 100 y 1000 filas: puede ver que la consulta de 1000 filas está muy fuera de línea con las demás: Resumen de la declaración

Puede ver que la tercera consulta tiene un número ridículo de lecturas, por lo que obviamente está haciendo algo completamente diferente. Aquí está el plan de ejecución estimado, con recuentos de filas. Plan de ejecución estimado de 1000 filas: Plan de ejecución estimado de 1000 filas

Y aquí están los resultados reales del plan de ejecución (por cierto, por "nunca termina", resulta que quise decir "termina en una hora"). Plan de ejecución real de 1000 filas Plan de ejecución real de 1000 filas

La primera cosa que noté fue que, en vez de tirar 60K filas de la tabla DimTime como se espera, en realidad tirando de 1,6 mil millones, con un B . Mirando mi consulta, no estoy seguro de cómo está retirando tantas filas de la tabla dimTime. El operador ENTRE que estoy usando solo se asegura de que estoy sacando el registro correcto de #mac en función del registro de tiempo en la tabla de hechos. Sin embargo, cuando agrego una línea a la cláusula WHERE donde filtro t.TimeValue (o t.TimeID) a un solo valor, puedo actualizar con éxito 100.000 filas en cuestión de segundos. Como resultado de eso, y como se dejó en claro en los planes de ejecución que incluí, es obvio que es mi horario el problema, pero no estoy seguro de cómo cambiaría los criterios de combinación para solucionar este problema y mantener la precisión . ¿Alguna idea?

Como referencia, aquí el plan (con recuentos de filas) para la actualización de 100 filas. Puede ver que alcanza el mismo índice, y aún con una tonelada de filas, pero en ninguna parte cerca de la misma magnitud de un problema. Ejecución de 100 filas con recuento de filas : ingrese la descripción de la imagen aquí

SqlRyan
fuente
Es GOTTA Be estadísticas. ¿Has ejecutado un sp_updatestatisticssobre la mesa?
JNK
@JNK: Inicialmente pensé que sí, pero ya he ejecutado sp_updatestats sin ningún cambio. Simplemente lo ejecuté nuevamente y no me importó actualizar las estadísticas en ninguno de los índices involucrados en la consulta. Gracias sin embargo!
SqlRyan
El segundo es un plan de actualización amplio (por índice) en lugar de uno estrecho (por fila) que explica parte de la complejidad visible adicional. Pero realmente la única diferencia es unir orden from #mac sma join f on f.macid = sma.macid join dimTime t on f.TimeID = t.TimeID and t.TimeValue between sma.StartDate and sma.enddatevsfrom #mac join t on t.TimeValue between sma.StartDate and sma.enddate join f on f.TimeID = t.TimeID and f.macid = sma.macid
Martin Smith
Algo no está bien aqui. Incluso el costoso plan de consultas debería generar filas de forma incremental. A TOP 50aún debe ejecutarse rápidamente. ¿Puedes subir los planes XML? Necesito mirar los recuentos de filas. ¿Se puede ejecutar TOP 50con maxdop 1 y como una selección, no como una actualización y publicar el plan? (Intentando simplificar / bisecar el espacio de búsqueda).
Usr
@usr unirse t.TimeValue between sma.StartDate and sma.enddatepuede terminar generando muchas más filas inútiles que luego se filtran en la unión contra FactSubscriber y, por lo tanto, no terminan en el resultado final.
Martin Smith

Respuestas:

3

El índice en dimTime está cambiando. El plan más rápido está utilizando un índice _dta. En primer lugar, asegúrese de que no esté marcado como un índice hipotético en sys.indexes.

Pensando que podría pasar por alto alguna parametrización utilizando la tabla #mac para filtrar en lugar de simplemente proporcionar las fechas de inicio / finalización como esta DONDE t.TimeValue entre @StartDate y @enddate. Deshágase de esa tabla temporal.

william_a_dba
fuente
El índice prefijado de dta parece que fue creado siguiendo una recomendación de DTA sin personalizar el nombre. Los índices hipotéticos no pueden aparecer en los planes de ejecución reales (y tampoco se estimarán sin algunos comandos no documentados). No estoy seguro de cómo funcionaría su segunda sugerencia. t.TimeValue between sma.StartDate and sma.enddateestá correlacionado, por lo que puede cambiar para cada fila de la #temptabla. ¿Con qué lo reemplazaría el OP?
Martin Smith
Justo, no presté suficiente atención a la mesa temporal.
william_a_dba
1
Sin embargo, los índices hipotéticos pueden arruinar un plan de ejecución. Si es hipotético, debe dejarse caer y recrearse. blogs.technet.com/b/anurag_sharma/archive/2008/04/15/…
william_a_dba
Los índices hipotéticos quedan cuando el DTA no termina / se congela antes de completarse. Debe limpiarlos manualmente si hay algún problema con el DTA.
william_a_dba
1
@william_a_dba - Ah, entiendo lo que quieres decir ahora (después de leer tu enlace). La consulta nunca termina podría haber sido continuamente recompilando. ¡Interesante!
Martin Smith
1

Sin más información sobre los recuentos de filas en el plan, mi recomendación preliminar es organizar el orden de unión correcto en la consulta y forzarlo a usar OPTION (FORCE ORDER). Aplicar el orden de unión del primer plan.

usr
fuente