sql >> Base de Datos >  >> RDS >> Database

UNION ALL Optimización

La concatenación de dos o más conjuntos de datos se expresa más comúnmente en T-SQL usando UNION ALL cláusula. Dado que el optimizador de SQL Server a menudo puede reordenar cosas como uniones y agregados para mejorar el rendimiento, es bastante razonable esperar que SQL Server también considere reordenar las entradas de concatenación, donde esto supondría una ventaja. Por ejemplo, el optimizador podría considerar los beneficios de reescribir A UNION ALL B como B UNION ALL A .

De hecho, el optimizador de SQL Server no hacer esto. Más precisamente, hubo cierto soporte limitado para el reordenamiento de entrada de concatenación en las versiones de SQL Server hasta 2008 R2, pero esto fue eliminado. en SQL Server 2012 y no ha vuelto a aparecer desde entonces.

Servidor SQL 2008 R2

Intuitivamente, el orden de las entradas de concatenación solo importa si hay un objetivo de fila . De manera predeterminada, SQL Server optimiza los planes de ejecución sobre la base de que todas las filas calificadas se devolverán al cliente. Cuando un objetivo de fila está en vigor, el optimizador intenta encontrar un plan de ejecución que produzca las primeras filas rápidamente.

Los objetivos de fila se pueden establecer de varias maneras, por ejemplo, usando TOP , un FAST n sugerencia de consulta, o usando EXISTS (que por su naturaleza necesita encontrar como máximo una fila). Cuando no hay un objetivo de fila (es decir, el cliente requiere todas las filas), generalmente no importa en qué orden se leen las entradas de concatenación:cada entrada se procesará por completo eventualmente en cualquier caso.

El soporte limitado en versiones hasta SQL Server 2008 R2 se aplica cuando hay un objetivo de exactamente una fila . En esta circunstancia específica, SQL Server reordenará las entradas de concatenación en función del costo esperado.

Esto no se hace durante la optimización basada en costos (como cabría esperar), sino como una reescritura posterior a la optimización de última hora de la salida normal del optimizador. Este arreglo tiene la ventaja de no aumentar el espacio de búsqueda del plan basado en costos (potencialmente una alternativa para cada posible reordenación), al mismo tiempo que produce un plan que está optimizado para devolver la primera fila rápidamente.

Ejemplos

Los siguientes ejemplos utilizan dos tablas con contenidos idénticos:Un millón de filas de números enteros del uno al millón. Una tabla es un montón sin índices no agrupados; el otro tiene un índice agrupado único:

CREATE TABLE dbo.Expensive
(
    Val bigint NOT NULL
);
 
CREATE TABLE dbo.Cheap
(
    Val bigint NOT NULL, 
 
    CONSTRAINT [PK dbo.Cheap Val]
        UNIQUE CLUSTERED (Val)
);
GO
INSERT dbo.Cheap WITH (TABLOCKX)
    (Val)
SELECT TOP (1000000)
    Val = ROW_NUMBER() OVER (ORDER BY SV1.number)
FROM master.dbo.spt_values AS SV1
CROSS JOIN master.dbo.spt_values AS SV2
ORDER BY
    Val
OPTION (MAXDOP 1);
GO
INSERT dbo.Expensive WITH (TABLOCKX)
    (Val)
SELECT
    C.Val
FROM dbo.Cheap AS C
OPTION (MAXDOP 1);

Gol sin fila

La siguiente consulta busca las mismas filas en cada tabla y devuelve la concatenación de los dos conjuntos:

SELECT E.Val 
FROM dbo.Expensive AS E 
WHERE 
    E.Val BETWEEN 751000 AND 751005
 
UNION ALL
 
SELECT C.Val
FROM dbo.Cheap AS C 
WHERE 
    C.Val BETWEEN 751000 AND 751005;

El plan de ejecución producido por el optimizador de consultas es:

La advertencia en la raíz SELECT El operador nos está alertando sobre el índice faltante obvio en la tabla del montón. Sentry One Plan Explorer agrega la advertencia en el operador Table Scan. Está llamando nuestra atención sobre el costo de E/S del predicado residual oculto dentro del escaneo.

El orden de las entradas a la Concatenación no importa aquí, porque no hemos establecido un objetivo de fila. Ambas entradas se leerán por completo para devolver todas las filas de resultados. De interés (aunque esto no está garantizado) tenga en cuenta que el orden de las entradas sigue el orden textual de la consulta original. Observe también que tampoco se especifica el orden de las filas de resultados finales, ya que no usamos un ORDER BY de nivel superior cláusula. Asumiremos que es deliberado y que el pedido final no tiene consecuencias para la tarea en cuestión.

Si invertimos el orden escrito de las tablas en la consulta así:

SELECT C.Val
FROM dbo.Cheap AS C 
WHERE 
    C.Val BETWEEN 751000 AND 751005
 
UNION ALL
 
SELECT E.Val 
FROM dbo.Expensive AS E 
WHERE 
    E.Val BETWEEN 751000 AND 751005;

El plan de ejecución sigue el cambio, accediendo primero a la tabla agrupada (nuevamente, esto no está garantizado):

Se puede esperar que ambas consultas tengan las mismas características de rendimiento, ya que realizan las mismas operaciones, solo que en un orden diferente.

Con un gol de fila

Claramente, la falta de indexación en la tabla del montón normalmente hará que encontrar filas específicas sea más costoso, en comparación con la misma operación en la tabla agrupada. Si le pedimos al optimizador un plan que devuelva la primera fila rápidamente, esperaríamos que SQL Server reordene las entradas de concatenación para que se consulte primero la tabla agrupada barata.

Usando la consulta que menciona primero la tabla del montón y usando una sugerencia de consulta FAST 1 para especificar el objetivo de la fila:

SELECT E.Val 
FROM dbo.Expensive AS E 
WHERE 
    E.Val BETWEEN 751000 AND 751005
 
UNION ALL
 
SELECT C.Val
FROM dbo.Cheap AS C 
WHERE 
    C.Val BETWEEN 751000 AND 751005
OPTION (FAST 1);

El plan de ejecución estimado producido en una instancia de SQL Server 2008 R2 es:

Observe que las entradas de concatenación se han reordenado para reducir el costo estimado de devolver la primera fila. Tenga en cuenta también que el índice faltante y las advertencias de E/S residuales han desaparecido. Ningún problema tiene importancia con esta forma de plan cuando el objetivo es devolver una sola fila lo más rápido posible.

La misma consulta ejecutada en SQL Server 2016 (usando cualquier modelo de estimación de cardinalidad) es:

SQL Server 2016 no ha reordenado las entradas de concatenación. La advertencia de E/S de Plan Explorer ha regresado, pero lamentablemente el optimizador no ha producido una advertencia de índice faltante esta vez (aunque es relevante).

Reordenamiento general

Como se mencionó, la reescritura posterior a la optimización que reordena las entradas de concatenación solo es efectiva para:

  • SQL Server 2008 R2 y versiones anteriores
  • Un objetivo de fila de exactamente uno

Si realmente queremos que solo se devuelva una fila, en lugar de un plan optimizado para devolver la primera fila rápidamente (pero que finalmente devolverá todas las filas), podemos usar un TOP cláusula con una tabla derivada o una expresión de tabla común (CTE):

SELECT TOP (1)
    UA.Val
FROM
(
    SELECT E.Val 
    FROM dbo.Expensive AS E 
    WHERE 
        E.Val BETWEEN 751000 AND 751005
 
    UNION ALL
 
    SELECT C.Val
    FROM dbo.Cheap AS C 
    WHERE 
        C.Val BETWEEN 751000 AND 751005
) AS UA;

En SQL Server 2008 R2 o anterior, esto produce el plan óptimo de entrada reordenada:

En SQL Server 2012, 2014 y 2016 no se produce ningún reordenamiento posterior a la optimización:

Si queremos que se devuelva más de una fila, por ejemplo usando TOP (2) , la reescritura deseada no se aplicará en SQL Server 2008 R2 incluso si un FAST 1 También se utiliza la sugerencia. En esa situación, debemos recurrir a trucos como usar TOP con una variable y un OPTIMIZE FOR pista:

DECLARE @TopRows bigint = 2; -- Number of rows actually needed
 
SELECT TOP (@TopRows)
    UA.Val
FROM
(
    SELECT E.Val 
    FROM dbo.Expensive AS E 
    WHERE 
        E.Val BETWEEN 751000 AND 751005
 
    UNION ALL
 
    SELECT C.Val
    FROM dbo.Cheap AS C 
    WHERE 
        C.Val BETWEEN 751000 AND 751005
) AS UA
OPTION (OPTIMIZE FOR (@TopRows = 1)); -- Just a hint

La sugerencia de consulta es suficiente para establecer un objetivo de fila de uno, mientras que el valor de tiempo de ejecución de la variable garantiza que se devuelva el número deseado de filas (2).

El plan de ejecución real en SQL Server 2008 R2 es:

Ambas filas devueltas provienen de la entrada de búsqueda reordenada, y Table Scan no se ejecuta en absoluto. Plan Explorer muestra los recuentos de filas en rojo porque la estimación fue para una fila (debido a la sugerencia), mientras que se encontraron dos filas en tiempo de ejecución.

Sin UNIÓN TODOS

Este problema tampoco se limita a consultas escritas explícitamente con UNION ALL . Otras construcciones como EXISTS y OR también puede resultar en que el optimizador introduzca un operador de concatenación, que puede verse afectado por la falta de reordenamiento de entrada. Hubo una pregunta reciente sobre el intercambio de pilas de administradores de bases de datos con exactamente este problema. Transformando la consulta de esa pregunta para usar nuestras tablas de ejemplo:

SELECT
    CASE
        WHEN 
            EXISTS 
            (
                SELECT 1
                FROM dbo.Expensive AS E
                WHERE E.Val BETWEEN 751000 AND 751005
            ) 
            OR EXISTS 
            (
                SELECT 1
                FROM dbo.Cheap AS C 
                WHERE C.Val BETWEEN 751000 AND 751005
            ) 
            THEN 1
        ELSE 0
    END;

El plan de ejecución en SQL Server 2016 tiene la tabla de montón en la primera entrada:

En SQL Server 2008 R2, el orden de las entradas está optimizado para reflejar el objetivo de fila única de la combinación semi:

En el plan más óptimo, la exploración del montón nunca se ejecuta.

Soluciones alternativas

En algunos casos, será evidente para el escritor de consultas que una de las entradas de concatenación siempre será más económica de ejecutar que las otras. Si eso es cierto, es bastante válido reescribir la consulta para que las entradas de concatenación más baratas aparezcan primero en el orden escrito. Por supuesto, esto significa que el escritor de consultas debe ser consciente de esta limitación del optimizador y estar preparado para confiar en un comportamiento no documentado.

Surge un problema más difícil cuando el costo de las entradas de concatenación varía según las circunstancias, tal vez dependiendo de los valores de los parámetros. Usando OPTION (RECOMPILE) no ayudará en SQL Server 2012 o posterior. Esa opción puede ser útil en SQL Server 2008 R2 o anterior, pero solo si también se cumple el requisito del objetivo de fila única.

Si hay dudas acerca de confiar en el comportamiento observado (entradas de concatenación del plan de consulta que coinciden con el orden textual de la consulta), se puede usar una guía de plan para forzar la forma del plan. Cuando diferentes órdenes de entrada sean óptimos para diferentes circunstancias, se pueden usar múltiples guías de planes, donde las condiciones se pueden codificar con precisión por adelantado. Sin embargo, esto no es lo ideal.

Reflexiones finales

De hecho, el optimizador de consultas de SQL Server contiene un basado en costos regla de exploración, UNIAReorderInputs , que es capaz de generar variaciones de orden de entrada de concatenación y explorar alternativas durante la optimización basada en costos (no como una reescritura posterior a la optimización de un solo intento).

Esta regla no está actualmente habilitada para uso general. Por lo que puedo decir, solo se activa cuando una guía de plan o USE PLAN la pista está presente. Esto permite que el motor fuerce con éxito un plan que se generó para una consulta que calificó para la reescritura de reordenamiento de entrada, incluso cuando la consulta actual no califica.

Mi sensación es que esta regla de exploración se limita deliberadamente a este uso, porque las consultas que se beneficiarían del reordenamiento de entrada de concatenación como parte de la optimización basada en costos no se consideran lo suficientemente comunes, o quizás porque existe la preocupación de que el esfuerzo adicional no sería rentable. apagado. Mi propia opinión es que el reordenamiento de entrada del operador de concatenación siempre debe explorarse cuando un objetivo de fila está en vigor.

También es una pena que la reescritura posterior a la optimización (más limitada) no sea efectiva en SQL Server 2012 o posterior. Esto podría deberse a un error sutil, pero no pude encontrar nada al respecto en la documentación, la base de conocimiento o en Connect. He añadido un nuevo elemento Connect aquí.

Actualización del 9 de agosto de 2017 :Esto ahora está arreglado bajo el indicador de seguimiento 4199 para SQL Server 2014 y 2016, consulte KB 4023419:

REVISIÓN:la consulta con UNION ALL y un objetivo de fila puede ejecutarse más lentamente en SQL Server 2014 o versiones posteriores en comparación con SQL Server 2008 R2