Index no hace que la ejecución sea más rápida y, en algunos casos, ralentiza la consulta. ¿Por que es esto entonces?

34

Estaba experimentando con índices para acelerar las cosas, pero en caso de una unión, el índice no mejora el tiempo de ejecución de la consulta y, en algunos casos, ralentiza las cosas.

La consulta para crear una tabla de prueba y llenarla con datos es:

CREATE TABLE [dbo].[IndexTestTable](
    [id] [int] IDENTITY(1,1) PRIMARY KEY,
    [Name] [nvarchar](20) NULL,
    [val1] [bigint] NULL,
    [val2] [bigint] NULL)

DECLARE @counter INT;
SET @counter = 1;

WHILE @counter < 500000
BEGIN
    INSERT INTO IndexTestTable
      (
        -- id -- this column value is auto-generated
        NAME,
        val1,
        val2
      )
    VALUES
      (
        'Name' + CAST((@counter % 100) AS NVARCHAR),
        RAND() * 10000,
        RAND() * 20000
      );

    SET @counter = @counter + 1;
END

-- Index in question
CREATE NONCLUSTERED INDEX [IndexA] ON [dbo].[IndexTestTable]
(
    [Name] ASC
)
INCLUDE (   [id],
    [val1],
    [val2])

Ahora la consulta 1, que se mejora (solo un poco, pero la mejora es consistente) es:

SELECT *
FROM   IndexTestTable I1
       JOIN IndexTestTable I2
            ON  I1.ID = I2.ID
WHERE  I1.Name = 'Name1'

Estadísticas y plan de ejecución sin índice (en este caso, la tabla utiliza el índice agrupado predeterminado):

(5000 row(s) affected)
Table 'IndexTestTable'. Scan count 2, logical reads 5580, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

(1 row(s) affected)

 SQL Server Execution Times:
   CPU time = 109 ms,  elapsed time = 294 ms.

ingrese la descripción de la imagen aquí

Ahora con el índice habilitado:

(5000 row(s) affected)
Table 'IndexTestTable'. Scan count 2, logical reads 2819, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

(1 row(s) affected)

 SQL Server Execution Times:
   CPU time = 94 ms,  elapsed time = 231 ms.

ingrese la descripción de la imagen aquí

Ahora la consulta que se ralentiza debido al índice (la consulta no tiene sentido ya que se creó solo para pruebas):

SELECT I1.Name,
       SUM(I1.val1),
       SUM(I1.val2),
       MIN(I2.Name),
       SUM(I2.val1),
       SUM(I2.val2)
FROM   IndexTestTable I1
       JOIN IndexTestTable I2
            ON  I1.Name = I2.Name
WHERE   
       I2.Name = 'Name1'
GROUP BY
       I1.Name

Con el índice agrupado habilitado:

(1 row(s) affected)
Table 'IndexTestTable'. Scan count 4, logical reads 60, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Worktable'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Worktable'. Scan count 1, logical reads 155106, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

(1 row(s) affected)

 SQL Server Execution Times:
   CPU time = 17207 ms,  elapsed time = 17337 ms.

ingrese la descripción de la imagen aquí

Ahora con el índice deshabilitado:

(1 row(s) affected)
Table 'IndexTestTable'. Scan count 5, logical reads 8642, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Worktable'. Scan count 2, logical reads 165212, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Worktable'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

(1 row(s) affected)

 SQL Server Execution Times:
   CPU time = 17691 ms,  elapsed time = 9073 ms.

ingrese la descripción de la imagen aquí

Las preguntas son:

  1. Aunque el índice es sugerido por SQL Server, ¿por qué ralentiza las cosas por una diferencia significativa?
  2. ¿Qué es la unión de Nested Loop que está tomando la mayor parte del tiempo y cómo mejorar su tiempo de ejecución?
  3. ¿Hay algo que estoy haciendo mal o me he perdido?
  4. Con el índice predeterminado (solo en la clave primaria), ¿por qué lleva menos tiempo? El índice ha sido creado. Esto se refleja en el plan de ejecución de la consulta y el costo de Index Seek es menor cuando IndexA está activo, pero ¿por qué sigue siendo más lento? Además, ¿qué hay en la unión externa izquierda de Nested Loop que está causando la desaceleración?

Usando SQL Server 2012

SpeedBirdNine
fuente

Respuestas:

23

Aunque el índice es sugerido por SQL Server, ¿por qué ralentiza las cosas por una diferencia significativa?

Las sugerencias de índice las realiza el optimizador de consultas. Si se encuentra con una selección lógica de una tabla que no está bien servida por un índice existente, puede agregar una sugerencia de "índice faltante" a su salida. Estas sugerencias son oportunistas; no se basan en un análisis completo de la consulta y no tienen en cuenta consideraciones más amplias. En el mejor de los casos, son una indicación de que puede ser posible una indexación más útil, y un DBA experto debería echar un vistazo.

Otra cosa que decir sobre las sugerencias de índice que faltan es que se basan en el modelo de costos del optimizador, y el optimizador estima en cuánto el índice sugerido podría reducir el costo estimado de la consulta. Las palabras clave aquí son "modelo" y "estimaciones". El optimizador de consultas sabe poco acerca de su configuración de hardware u otras opciones de configuración del sistema: su modelo se basa en gran medida en números fijos que producen resultados de planes razonables para la mayoría de las personas en la mayoría de los sistemas la mayor parte del tiempo. Además de los problemas con los números de costo exactos utilizados, los resultados son siempre estimaciones, y las estimaciones pueden ser incorrectas.

¿Qué es la unión de Nested Loop que está tomando la mayor parte del tiempo y cómo mejorar su tiempo de ejecución?

Hay poco por hacer para mejorar el rendimiento de la operación de unión cruzada; Los bucles anidados son la única implementación física posible para una unión cruzada. El carrete de la mesa en el lado interno de la unión es una optimización para evitar volver a escanear el lado interno de cada fila externa. Si esta es una optimización de rendimiento útil depende de varios factores, pero en mis pruebas la consulta está mejor sin ella. Nuevamente, esto es una consecuencia del uso de un modelo de costo: mi CPU y sistema de memoria probablemente tengan características de rendimiento diferentes a las suyas. No hay una sugerencia de consulta específica para evitar el spool de la tabla, pero hay un indicador de seguimiento no documentado (8690) que puede usar para probar el rendimiento de ejecución con y sin el spool. Si esto fuera un problema real del sistema de producción, el plan sin el spool podría forzarse utilizando una guía de plan basada en el plan producido con TF 8690 habilitado. No se recomienda el uso de indicadores de seguimiento no documentados en la producción porque la instalación deja de ser técnicamente compatible y los indicadores de seguimiento pueden tener efectos secundarios indeseables.

¿Hay algo que estoy haciendo mal o me he perdido?

Lo principal que falta es que, aunque el plan que utiliza el índice no agrupado tiene un costo estimado más bajo de acuerdo con el modelo del optimizador, tiene un problema significativo en el tiempo de ejecución. Si observa la distribución de filas a través de subprocesos en el plan utilizando el Índice agrupado, es probable que vea una distribución razonablemente buena:

Plan de escaneo

En el plan que usa la búsqueda de índice no agrupado, el trabajo termina siendo realizado completamente por un hilo:

Buscar plan

Esto es una consecuencia de la forma en que el trabajo se distribuye entre los hilos mediante operaciones paralelas de exploración / búsqueda. No siempre es el caso que un escaneo paralelo distribuya el trabajo mejor que una búsqueda de índice, pero lo hace en este caso. Los planes más complejos pueden incluir intercambiar reparticiones para redistribuir el trabajo a través de subprocesos. Este plan no tiene tales intercambios, por lo que una vez que las filas se asignan a un hilo, todo el trabajo relacionado se realiza en ese mismo hilo. Si observa la distribución de trabajo para los otros operadores en el plan de ejecución, verá que todo el trabajo se realiza por el mismo hilo que se muestra para la búsqueda de índice.

No hay sugerencias de consulta que afecten la distribución de filas entre subprocesos, lo importante es ser consciente de la posibilidad y poder leer suficientes detalles en el plan de ejecución para determinar cuándo está causando un problema.

Con el índice predeterminado (solo en la clave primaria), ¿por qué tarda menos tiempo? Y con el índice no agrupado presente, para cada fila de la tabla de unión, la fila de la tabla unida se debe encontrar más rápido, porque la unión está en la columna Nombre en la que El índice ha sido creado. Esto se refleja en el plan de ejecución de la consulta y el costo de Index Seek es menor cuando IndexA está activo, pero ¿por qué sigue siendo más lento? Además, ¿qué hay en la unión externa izquierda de Nested Loop que está causando la desaceleración?

Ahora debería quedar claro que el plan de índice no agrupado es potencialmente más eficiente, como era de esperar; Es solo la distribución deficiente del trabajo entre los hilos en el momento de la ejecución lo que explica el problema de rendimiento.

En aras de completar el ejemplo e ilustrar algunas de las cosas que he mencionado, una forma de obtener una mejor distribución del trabajo es utilizar una tabla temporal para impulsar la ejecución paralela:

SELECT
    val1,
    val2
INTO #Temp
FROM dbo.IndexTestTable AS ITT
WHERE Name = N'Name1';

SELECT 
    N'Name1',
    SUM(T.val1),
    SUM(T.val2),
    MIN(I2.Name),
    SUM(I2.val1),
    SUM(I2.val2)
FROM   #Temp AS T
CROSS JOIN IndexTestTable I2
WHERE
    I2.Name = 'Name1'
OPTION (FORCE ORDER, QUERYTRACEON 8690);

DROP TABLE #Temp;

Esto da como resultado un plan que utiliza las búsquedas de índice más eficientes, no presenta un carrete de tabla y distribuye bien el trabajo entre los hilos:

Plan óptimo

En mi sistema, este plan se ejecuta significativamente más rápido que la versión Clustered Index Scan.

Si está interesado en obtener más información sobre los aspectos internos de la ejecución de consultas paralelas, es posible que desee ver la grabación de mi sesión PASS Summit 2013 .

Paul White dice GoFundMonica
fuente
0

No se trata realmente del índice, es más una consulta mal escrita. Tiene solo 100 valores únicos de nombre, esto deja un recuento único de 5000 por nombre.

Entonces, para cada línea de la tabla 1, unirá 5000 de la tabla 2. ¿Puede decir 25020004 líneas?

Pruebe esto, tenga en cuenta que esto es con solo 1 índice, el que usted enumeró.

    DECLARE @Distincts INT
    SET @Distincts = (SELECT  TOP 1 COUNT(*) FROM IndexTestTable I1 WHERE I1.Name = 'Name1' GROUP BY I1.Name)
    SELECT I1.Name
    , @Distincts
    , SUM(I1.val1) * @Distincts
    , SUM(I1.val2) * @Distincts
    , MIN(I2.Name)
    , SUM(I2.val1)
    , SUM(I2.val2)
    FROM   IndexTestTable I1
    LEFT OUTER JOIN

    (
        SELECT I2.Name
        , SUM(I2.val1) val1
        , SUM(I2.val2) val2
        FROM IndexTestTable I2
        GROUP BY I2.Name
    ) I2 ON  I1.Name = I2.Name
    WHERE I1.Name = 'Name1'
    GROUP BY  I1.Name

Y tiempo:

    SQL Server parse and compile time: 
       CPU time = 0 ms, elapsed time = 8 ms.
    Table 'IndexTestTable'. Scan count 1, logical reads 31, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

     SQL Server Execution Times:
       CPU time = 0 ms,  elapsed time = 1 ms.

    (1 row(s) affected)
    Table 'IndexTestTable'. Scan count 2, logical reads 62, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
    Table 'Worktable'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

     SQL Server Execution Times:
       CPU time = 16 ms,  elapsed time = 10 ms.

ingrese la descripción de la imagen aquí

No puede culpar a los índices SQL por consultas mal formadas

Adrian Sullivan
fuente
1
Gracias por la respuesta, y sí, la consulta se puede mejorar, pero la lógica de mi pregunta fue que con el índice predeterminado (solo en la clave primaria) ¿por qué tarda menos tiempo y con el índice no agrupado presente para cada fila en la tabla de unión, la fila de la tabla unida se debe encontrar más rápido, lo que se refleja en el plan de ejecución de la consulta y el costo de Index Seek es menor cuando IndexA está activo, pero ¿por qué aún más lento? Además, ¿qué hay en la unión externa izquierda del bucle anidado que está causando la desaceleración? He editado la pregunta para agregar este comentario, para que la pregunta sea más clara.