SQL Select toma demasiado tiempo para ejecutarse

9

Es una selección simple de una tabla temporal, uniendo a la izquierda una tabla existente en su clave primaria, con dos sub-selecciones usando el top 1 que hace referencia a la tabla unida.

En codigo:

SELECT
    TempTable.Col1,
    TempTable.Col2,
    TempTable.Col3,
    JoinedTable.Col1,
    JoinedTable.Col2,
    (
        SELECT TOP 1
            ThirdTable.Col1 -- Which is ThirdTable's Primary Key
        FROM
            ThirdTable
        WHERE
            ThirdTable.SomeColumn = JoinedTable.SomeColumn
    ) as ThirdTableColumn1,
    (
        SELECT TOP 1
            ThirdTable.Col1 -- Which is also ThirdTable's Primary Key
        FROM
            ThirdTable
        WHERE
            ThirdTable.SomeOtherColumn = JoinedTable.SomeColumn
    ) as ThirdTableColumn2,
FROM
    #TempTable as TempTable
LEFT JOIN
    JoinedTable
ON (TempTable.PKColumn1 = JoinedTable.PKColumn1 AND 
    TempTable.PKColumn2 = JoinedTable.PKColumn2)
WHERE
    JoinedTable.WhereColumn IN  (1, 3)

Esta es una réplica exacta de mi consulta.

Si elimino las dos subelecciones, se ejecuta muy bien y rápidamente. Con las dos sub-selecciones, obtengo alrededor de 100 registros por segundo, lo cual es extremadamente lento para esta consulta porque debería devolver casi un millón de registros.

Verifiqué si cada tabla tiene una Clave primaria, todas las tienen. Todos tienen índices Y estadísticas para sus columnas importantes, como las de las cláusulas WHERE y las de la cláusula JOIN. La única tabla sin clave primaria definida ni índice es la tabla temporal, pero tampoco es el problema porque no está relacionada con las subselecciones lentas y, como mencioné, sin subselecciones, funciona bien.

Sin ellos TOP 1, devuelve más de un resultado y genera un error.

Ayuda, alguien?

EDITAR :

Entonces el plan de ejecución me dijo que me faltaba un índice. Lo creé y recreé algunos de los otros índices. Después de un tiempo, el plan de ejecución los estaba usando, y la consulta ahora se ejecuta rápidamente. El único problema es que no logro volver a hacerlo en otro servidor, para la misma consulta. Entonces, mi solución será SUGERIR qué índice utilizará SQL Server.

Smur
fuente
Wow, eso es impresionante. Pero, ¿puedes dividir esto en varias declaraciones separadas? Además, ¿qué tal los procedimientos almacenados en su lugar?
2
@Adel Esa selección es, de hecho, una subselección dentro de un Procedimiento almacenado. En realidad, todo es bastante grande, pero estoy 100% seguro de que esa es la parte exacta que lleva tiempo ejecutar.
El cambio en el plan de ejecución, incluidos los índices seleccionados automáticamente, probablemente tenga que ver con un cambio en los datos. Me aseguraría de que sus índices cubran por completo o el motor tomará caminos inesperados, como un escaneo de tabla. Sugiero revisar el plan de ejecución en el nuevo servidor (sin sugerencias) para ver dónde experimenta desviaciones del sistema original.
Robert Miller
Veo. Solo cambié el servidor, la base de datos es la misma, con los mismos índices. Aún así, no parece elegir automáticamente usar mis índices. Hace exactamente lo que dijiste: un escaneo de tabla.
Smur
Parece que el optimizador de consultas no le gusta ninguno de los índices de la tabla para su consulta. ¿El plan de ejecución mostró un índice faltante?
Robert Miller

Respuestas:

7

Creo que en una consulta de un millón de registros, debes evitar cosas como OUTER JOINS. Te sugiero que uses en UNION ALLlugar de LEFT JOIN. Siempre que piense que CROSS APPLYes más eficiente que la subconsulta en la cláusula select, modificaré la consulta escrita por Conard Frix, que creo que es correcta.

Ahora: cuando empecé a modificar la consulta me di cuenta de que tiene una cláusula WHERE diciendo: JoinedTable.WhereColumn IN (1, 3). en este caso, si el campo es nulo, la condición se convertirá en falsa. entonces, ¿por qué está utilizando LEFT JOIN mientras filtra filas con valores nulos? simplemente reemplace LEFT JOINcon INNER JOIN, le garantizo que será más rápido.

sobre ÍNDICE:

tenga en cuenta que cuando tenga un índice en una tabla, diga

table1(a int, b nvarchar)

y tu índice es:

nonclustered index ix1 on table1(a)

y quieres hacer algo como esto:

select a,b from table1
where a < 10

en su índice no ha incluido la columna, b¿qué sucede?

si sql-server usa su índice, tendrá que buscar en el índice, llamado "Búsqueda de índice" y luego consultar la tabla principal para obtener la columna b, llamada "Buscar" . Este procedimiento puede llevar mucho más tiempo que escanear la tabla en sí: "Escaneo de tabla" .

pero en base a las estadísticas que tiene sql-server, en tales situaciones, es posible que no use su índice en absoluto.

así que antes que nada, verifique Execution Plansi el índice se usa en absoluto.

en caso afirmativo o no ambos, modifique su índice para incluir todas las columnas que está seleccionando. decir como:

nonclustered index ix1 on table1(a) include(b)

en este caso, Look Up no será necesario y su consulta se ejecutará mucho más rápido.


fuente
1
No puedo cambiar esa unión izquierda a la unión interna, arruinaría los resultados, es una regla de negocios: la segunda tabla no necesariamente tiene que tener un registro relacionado. Además, la columna en la cláusula WHERE no acepta valores nulos.
Smur
6

Es la sub selección en su selección de columna que está causando el lento retorno. Debería intentar usar sus sub-selecciones en combinaciones izquierdas, o usar una tabla derivada como la que he definido a continuación.

Usar combinaciones izquierdas en dos instancias de la tercera tabla

SELECT
  TempTable.Col1,
  TempTable.Col2,
  TempTable.Col3,
  JoinedTable.Col1,
  JoinedTable.Col2,
  ThirdTable.Col1 AS ThirdTableColumn1,
  ThirdTable2.Col1 AS ThirdTableColumn2
FROM #TempTable as TempTable
LEFT JOIN JoinedTable ON (TempTable.PKColumn1 = JoinedTable.PKColumn2 AND 
    TempTable.PKColumn 2 = JoinedTable.PKColumn2)
LEFT JOIN ThirdTable ON ThirdTable.SomeColumn = JoinedTable.SomeColumn
LEFT JOIN ThirdTable ThirdTable2 ON ThirdTable.SomeOtherColumn = JoinedTable.SomeColumn
WHERE
    JoinedTable.WhereColumn IN  (1, 3)

Usando una tabla derivada

 SELECT 
      TempTable.Col1,
      TempTable.Col2,
      TempTable.Col3,
      DerivedTable.Col1,
      DerivedTable.Col2,
      DerivedTable.ThirdTableColumn1,
      DerivedTable.ThirdTableColumn2
 FROM #TempTable as TempTable
    LEFT JOIN (SELECT
                 JoinedTable.PKColumn2,
                 JoinedTable.Col1,
                 JoinedTable.Col2,
                 JoinedTable.WhereColumn,
                 ThirdTable.Col1 AS ThirdTableColumn1,
                 ThirdTable2.Col1 AS ThirdTableColumn2
               FROM JoinedTable
               LEFT JOIN ThirdTable ON ThirdTable.SomeColumn = JoinedTable.SomeColumn
               LEFT JOIN ThirdTable ThirdTable2 ON ThirdTable.SomeOtherColumn = JoinedTable.SomeColumn) 
        DerivedTable ON (TempTable.PKColumn1 = DerivedTable .PKColumn2 AND 
        TempTable.PKColumn2 = DerivedTable.PKColumn2)
    WHERE
        DerivedTable.WhereColumn IN  (1, 3)
John Hartsock
fuente
2

Intente una aplicación cruzada en su lugar

SELECT
    TempTable.Col1,
    TempTable.Col2,
    TempTable.Col3,
    JoinedTable.Col1,
    JoinedTable.Col2,
    ThirdTableColumn1.col1,
    ThirdTableColumn2.col1

FROM
    #TempTable as TempTable
LEFT JOIN
    JoinedTable
ON (TempTable.PKColumn1 = JoinedTable.PKColumn2 AND 
    TempTable.PKColumn 2 = JoinedTablePKColumn2)

CROSS APPLY
(
        SELECT TOP 1
            ThirdTable.Col1 -- Which is ThirdTable's Primary Key
        FROM
            ThirdTable
        WHERE
            ThirdTable.SomeColumn = JoinedTable.SomeColumn
    ) as ThirdTableColumn1
CROSS APPLY    (
        SELECT TOP 1
            ThirdTable.Col1 -- Which is also ThirdTable's Primary Key
        FROM
            ThirdTable
        WHERE
            ThirdTable.SomeOtherColumn = JoinedTable.SomeColumn
    ) as ThirdTableColumn2,
WHERE
    JoinedTable.WhereColumn IN  (1, 3)

También puede usar CTE y row_number o una consulta en línea usando MIN

Conrad Frix
fuente
2

Mueva los bits JOIN fuera de la parte principal de la cláusula y póngalo como una subselección. Moverlo a la sección DONDE y UNIR le garantiza que no tiene que SELECCIONAR TOP 1 una y otra vez, lo cual creo que es la razón de la lentitud. Si desea verificar esto, examine el plan de ejecución.


fuente
2

Las ThirdTablereferencias (subselecciones en su ejemplo) necesitan la misma atención de índice que cualquier otra parte de una consulta.

Independientemente de si usa sub selecciones:

(
    SELECT TOP 1
        ThirdTable.Col1 -- Which is ThirdTable's Primary Key
    FROM
        ThirdTable
    WHERE
        ThirdTable.SomeColumn = JoinedTable.SomeColumn
) as ThirdTableColumn1,
(
    SELECT TOP 1
        ThirdTable.Col1 -- Which is also ThirdTable's Primary Key
    FROM
        ThirdTable
    WHERE
        ThirdTable.SomeOtherColumn = JoinedTable.SomeColumn
) as ThirdTableColumn2,

IZQUIERDA SE UNE (según lo propuesto por John Hartsock):

LEFT JOIN ThirdTable ON ThirdTable.SomeColumn = JoinedTable.SomeColumn
LEFT JOIN ThirdTable ThirdTable2 ON ThirdTable.SomeOtherColumn = JoinedTable.SomeColumn

APLICACIÓN CRUZADA (según lo propuesto por Conrad Frix):

CROSS APPLY
(
        SELECT TOP 1
            ThirdTable.Col1 -- Which is ThirdTable's Primary Key
        FROM
            ThirdTable
        WHERE
            ThirdTable.SomeColumn = JoinedTable.SomeColumn
    ) as ThirdTableColumn1
CROSS APPLY    (
        SELECT TOP 1
            ThirdTable.Col1 -- Which is also ThirdTable's Primary Key
        FROM
            ThirdTable
        WHERE
            ThirdTable.SomeOtherColumn = JoinedTable.SomeColumn
    ) as ThirdTableColumn2

Usted necesita asegurarse covering indexesestán definidos para ThirdTable.SomeColumny ThirdTable.SomeOtherColumny los índices es único. Esto significa que deberá calificar aún más las ThirdTablereferencias para eliminar la selección de varias filas y mejorar el rendimiento. La elección de sub selects, LEFT JOINo CROSS APPLYno importará realmente hasta que mejore la selectividad para ThirdTable.SomeColumne ThirdTable.SomeOtherColumnincluyendo más columnas para garantizar una selectividad única. Hasta entonces, espero que su desempeño continúe sufriendo.

El covering indextema es muy bien presentado por Maziar Taheri; Si bien no repito su trabajo, hago hincapié en la necesidad de tomar en serio el uso de índices de cobertura.

En resumen: mejore la selectividad para las consultas ThirdTable.SomeColumny ThirdTable.SomeOtherColumn(o combinaciones) agregando columnas relacionadas en la tabla para garantizar una coincidencia de fila única. Si esto no es posible, continuará sufriendo problemas de rendimiento ya que el motor está ocupado tirando en filas que posteriormente se tiran. Esto afecta su E / S, CPU y, en última instancia, el plan de ejecución.

Robert Miller
fuente