La consulta de ajuste en IF EXISTS lo hace muy lento

16

Tengo la siguiente consulta:

select databasename 
from somedb.dbo.bigtable l where databasename ='someval' and source  <>'kt'
and not exists(select 1 from dbo.smalltable c where c.source=l.source)

La consulta anterior se completa en tres segundos.

Si la consulta anterior devuelve algún valor, queremos que el procedimiento almacenado salga, así que lo reescribí como a continuación:

If Exists(
select databasename 
from somedb.dbo.bigtable l where databasename ='someval' and source  <>'kt'
and not exists(select 1 from dbo.smalltable c where c.source=l.source)
)
Begin
Raiserror('Source missing',16,1)
Return
End

Sin embargo, esto está tomando 10 minutos.

Puedo reescribir la consulta anterior como a continuación, que también se completa en menos de 3 segundos:

  select databasename 
from somedb.dbo.bigtable l where databasename ='someval' and source  <>'kt'
and not exists(select 1 from dbo.smalltable c where c.source=l.source
if @@rowcount >0
Begin
Raiserror('Source missing',16,1)
Return
End

El problema con la reescritura anterior es que la consulta anterior es parte de un procedimiento almacenado más grande y devuelve múltiples conjuntos de resultados. En C #, iteramos a través de cada conjunto de resultados y realizamos algunos procesamientos.

Lo anterior devuelve un conjunto de resultados vacío, por lo que si sigo este enfoque, tengo que cambiar mi C # y volver a implementar.

Entonces mi pregunta es:

¿Por qué usar solo IF EXISTScambia el plan para tomar tanto tiempo?

A continuación se encuentran los detalles que pueden ayudarlo y avíseme si necesita algún detalle:

  1. Crear tabla y script de estadísticas para obtener el mismo plan que el mío
  2. Plan de ejecución lenta
  3. Plan de ejecución rápida

    Plan lento con Brentozar Pegar el plan
    Plan rápido con Brentozar Pegar el plan

Nota: Ambas consultas son iguales (usando parámetros), la única diferencia es EXISTS(aunque podría haber cometido algunos errores al anonimizar).

Los scripts de creación de tablas están a continuación:

http://pastebin.com/CgSHeqXc - estadísticas de mesa pequeña
http://pastebin.com/GUu9KfpS - estadísticas de mesa grande

TheGameiswar
fuente
La discusión sobre esta pregunta se ha trasladado a esta sala de chat .
Paul White reinstala a Monica

Respuestas:

18

Como ha sido explicado por Paul White en su blog: En el interior del optimizador: Objetivos de fila en profundidad las EXISTSintroduce un gol fila, que prefiere NESTED LOOPSo MERGE JOINmás deHASH MATCH

Como último ejemplo, considere que una semiunión lógica (como una subconsulta introducida con EXISTS) comparte el tema general: debe optimizarse para encontrar rápidamente la primera fila coincidente.

En su consulta, esto aparentemente introduce bucles anidados y elimina el paralelismo, lo que resulta en un plan más lento.

Por lo tanto, es probable que necesite encontrar una forma de reescribir su consulta sin usar el NOT EXISTSde su consulta.

Puede salirse con la suya reescribiendo su consulta usando un LEFT OUTER JOINy comprobando que no había una fila en una tabla pequeña probandoNULL

If EXISTS(
    SELECT databasename
    FROM somedb.dbo.bigtable l
    LEFT JOIN dbo.smalltable c ON c.source = l.source
    WHERE databasename = 'someval'
    AND source <> 'kt'
    AND c.source IS NULL
)

Probablemente también podría usar una EXCEPTconsulta, dependiendo de cuántos campos necesite comparar de esta manera:

If EXISTS(
   SELECT source
   FROM somedb.dbo.bigtable l
   WHERE databasename = 'someval'
   AND source <> 'kt'

   EXCEPT

   SELECT source
   FROM dbo.smalltable
)

Eso sí, Aaron Bertrand tiene una publicación en el blog que explica los motivos por los que prefiere NO EXISTE, que debe leer para ver si otros enfoques funcionan mejor y para estar al tanto de los posibles problemas de corrección en caso de valores NULL.

Preguntas y respuestas relacionadas: SI EXISTE tarda más que la instrucción select incorporada

Tom V - Equipo Mónica
fuente
0

Debe volver a escribir su consulta utilizando combinaciones explícitas y especificar qué operación de combinación desea usar (bucle, hash o fusión) de esta manera.

If not exists(
    select databasename 
    from somedb.dbo.bigtable l
    inner hash join dbo.smalltable c 
        on c.source = l.source
where databasename ='someval' and source  <>'kt')
begin
    Raiserror('Source missing',16,1)
    Return
end

Cuando se utiliza EXISTS o NOT EXISTS, el plan de consulta generado por SQL Server con operación NESTED LOOP supone que debe pasar por todas las filas del conjunto una por una buscando la primera fila para satisfacer la condición. Usar HASH JOIN lo acelerará.

Artem Machnev
fuente
Que tú, lo
probarás
0

Me he encontrado con el mismo problema, me las arreglé para trabajar evitando el uso de "EXISTS" y haciendo uso de la función "COUNT ()" y la instrucción "IF ... ELSE".

Para su ejemplo intente lo siguiente:

IF
(
    SELECT
        COUNT(l.databasename) + 1 AS databasename
    FROM somedb.dbo.bigtable AS l

    WHERE   l.databasename ='someval'
        AND l.[source]  <> 'kt'
        AND NOT EXISTS(SELECT 1 FROM dbo.smalltable AS c WHERE c.[source]=l.[source])
) > 1 --Acts like EXISTS
BEGIN
    RAISERROR('Source missing', 16, 1)
RETURN
END

La razón por la que agrego "+ 1" al conteo es para que pueda usar "> 1" en la condición IF, usando "> 0" o "<> 0" activará la consulta para usar bucles anidados en lugar de HASH Partido. No he investigado por qué eso está sucediendo exactamente, sería interesante descubrir por qué.

¡Espero que ayude!

Hayder Nahee
fuente