¿Cómo hacer uso del modo Batch con UNPIVOT (una unión de bucle)?


12

Tengo una consulta de la siguiente forma:

SELECT ...
FROM ColumnstoreTable cs
CROSS APPLY (
    SELECT *
    FROM (VALUES
        ('A', cs.DataA)
        , ('B', cs.DataB)
        , ('C', cs.DataC)
    ) x(Col0, Col1)
) someValues

Esto toma cada fila de una subconsulta respaldada por Columnstore ( ColumnstoreTable) y multiplica esas filas. Esto es esencialmente un UNPIVOT. La consulta real es más grande que esto. Esta parte de la consulta se alimenta a otro procesamiento.

El problema aquí es que esto CROSS APPLYse implementa como una unión en bucle, que es una opción razonable. Desafortunadamente, las uniones de bucle no admiten el modo por lotes.

Esta parte de la consulta es muy crítica para el rendimiento y sospecho que ejecutarla en modo por lotes podría ser muy beneficiosa para el rendimiento.

¿Cómo puedo reescribir esta consulta para que no salga del modo por lotes?

Intenté usar una tabla temporal en lugar de VALUES, pero eso no cambió el hecho de que no hay una condición de unión de igualdad para unir hash.

Respuestas:


12

Un enfoque podría ser usar una tabla #temp para los valores y también introducir una columna de equijoin ficticia para permitir una unión hash. Por ejemplo:

-- Create a #temp table with a dummy column to match the hash join
-- and the actual column you want
CREATE TABLE #values (dummy INT NOT NULL, Col0 CHAR(1) NOT NULL)
INSERT INTO #values (dummy, Col0)
VALUES (0, 'A'),
        (0, 'B'),
        (0, 'C')
GO

-- A similar query, but with a dummy equijoin condition to allow for a hash join
SELECT v.Col0, 
    CASE v.Col0 
        WHEN 'A' THEN cs.DataA 
        WHEN 'B' THEN cs.DataB 
        WHEN 'C' THEN cs.DataC
        END AS Col1
FROM ColumnstoreTable cs
JOIN #values v
    -- Join your dummy column to any numeric column on the columnstore,
    -- multiplying that column by 0 to ensure a match to all #values
    ON v.dummy = cs.DataA * 0

Plan de rendimiento y consulta

Este enfoque produce un plan de consulta como el siguiente, y la coincidencia hash se realiza en modo por lotes:

ingrese la descripción de la imagen aquí

Si reemplazo la SELECTdeclaración con una SUMde la CASEdeclaración para evitar tener que transmitir todas esas filas a la consola y luego ejecutar la consulta en una tabla de almacén de columnas de fila real de 100MM que tengo por ahí, veo un rendimiento bastante bueno para generar los 300MM necesarios filas:

CPU time = 33803 ms, elapsed time = 4363 ms.

Y el plan real muestra una buena paralelización de la unión hash.

ingrese la descripción de la imagen aquí

Notas sobre la paralelización de combinación hash cuando todas las filas tienen el mismo valor

El rendimiento de esta consulta depende en gran medida de que cada subproceso en el lado de la sonda de la unión tenga acceso a la tabla hash completa (a diferencia de una versión particionada hash, que asignaría todas las filas a un solo subproceso dado que solo hay un valor distinto para la dummycolumna).

Afortunadamente, esto es cierto en este caso (como podemos ver por la falta de un Parallelismoperador en el lado de la sonda) y debería ser cierto de manera confiable porque el modo por lotes crea una única tabla hash que se comparte entre los subprocesos. Por lo tanto, cada subproceso puede tomar sus filas del Columnstore Index Scany combinarlas con esa tabla hash compartida. En SQL Server 2012, esta funcionalidad era mucho menos predecible porque un derrame causó que el operador se reiniciara en modo Fila, perdiendo el beneficio del modo por lotes y también requiriendo un Repartition Streamsoperador en el lado de la sonda de la unión, lo que causaría un sesgo de hilo en este caso . Permitir que los derrames permanezcan en modo por lotes es una mejora importante en SQL Server 2014.

Que yo sepa, el modo de fila no tiene esta capacidad de tabla hash compartida. Sin embargo, en algunos casos, generalmente con una estimación de menos de 100 filas en el lado de la compilación, SQL Server creará una copia separada de la tabla hash para cada subproceso (identificable por el Distribute Streamsinicio de la unión hash). Esto puede ser muy poderoso, pero es mucho menos confiable que el modo por lotes, ya que depende de sus estimaciones de cardinalidad y SQL Server está tratando de evaluar los beneficios versus el costo de construir una copia completa de la tabla hash para cada hilo.

UNION ALL: una alternativa más simple

Paul White señaló que otra opción, y potencialmente más simple, sería utilizar UNION ALLpara combinar las filas para cada valor. Esta es probablemente su mejor opción, suponiendo que sea fácil para usted construir este SQL dinámicamente. Por ejemplo:

SELECT 'A' AS Col0, c.DataA AS Col1
FROM ColumnstoreTable c
UNION ALL
SELECT 'B' AS Col0, c.DataB AS Col1
FROM ColumnstoreTable c
UNION ALL
SELECT 'C' AS Col0, c.DataC AS Col1
FROM ColumnstoreTable c

Esto también produce un plan que puede utilizar el modo por lotes y proporciona un rendimiento aún mejor que la respuesta original. (Aunque en ambos casos el rendimiento es lo suficientemente rápido como para que cualquier selección o escritura de datos en una tabla se convierta rápidamente en el cuello de botella). El UNION ALLenfoque también evita jugar juegos como multiplicar por 0. ¡A veces es mejor pensar de manera simple!

CPU time = 8673 ms, elapsed time = 4270 ms.

ingrese la descripción de la imagen aquí


Es bueno saber el truco de UNION ALL, pero falla en caso de que la consulta de origen no sea solo una tabla sino una consulta costosa en sí misma.
boot4life
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.