Come utilizzare la modalità batch con UNPIVOT (un loop loop)?


12

Ho una domanda del seguente modulo:

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

Questo prende ogni riga da una subquery ( ColumnstoreTable) supportata da Columnstore e moltiplica quelle righe. Questo è essenzialmente un UNPIVOT. La vera query è più grande di questa. Questa parte della query viene inserita in un'altra elaborazione.

Il problema qui è che questo CROSS APPLYè implementato come un loop loop che è una scelta ragionevole. Sfortunatamente, i join di loop non supportano la modalità batch.

Questa parte della query è molto critica in termini di prestazioni e sospetto che eseguirla in modalità batch potrebbe essere molto vantaggiosa per le prestazioni.

Come posso riscrivere questa query in modo da non passare dalla modalità batch?

Ho provato a utilizzare una tabella temporanea anziché VALUES, ma ciò non ha modificato il fatto che non esiste una condizione di uguaglianza di join su cui eseguire l'hash.

Risposte:


12

Un approccio potrebbe essere quello di utilizzare una tabella #temp per i valori e introdurre anche una colonna fittizia equijoin per consentire un hash join. Per esempio:

-- 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

Piano di prestazioni e query

Questo approccio produce un piano di query come il seguente e la corrispondenza hash viene eseguita in modalità batch:

inserisci qui la descrizione dell'immagine

Se si sostituisce la SELECTdichiarazione con una SUMdella CASEdichiarazione al fine di evitare di dover trasmettere tutte quelle file alla console e quindi eseguire la query su un vero e proprio 100MM tavolo fila columnstore ho in giro, vedo abbastanza buone prestazioni per generare il 300MM requisito righe:

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

E il piano reale mostra una buona parallelizzazione del join hash.

inserisci qui la descrizione dell'immagine

Note sulla parallelizzazione dei join hash quando tutte le righe hanno lo stesso valore

Le prestazioni di questa query dipendono fortemente da ogni thread sul lato sonda del join che ha accesso alla tabella hash completa (al contrario di una versione partizionata hash, che associ tutte le righe a un singolo thread dato che esiste un solo valore distinto per la dummycolonna).

Fortunatamente, questo è vero in questo caso (come possiamo vedere dalla mancanza di un Parallelismoperatore sul lato della sonda) e dovrebbe essere vero in modo affidabile perché la modalità batch crea una singola tabella hash condivisa tra i thread. Pertanto, ogni thread può prendere le loro righe da Columnstore Index Scane abbinarle a quella tabella hash condivisa. In SQL Server 2012, questa funzionalità era molto meno prevedibile perché una fuoriuscita causava il riavvio dell'operatore in modalità Row, perdendo entrambi i vantaggi della modalità batch e richiedendo anche un Repartition Streamsoperatore sul lato sonda del join che in questo caso causerebbe l'inclinazione del thread . Consentire agli sversamenti di rimanere in modalità batch è un notevole miglioramento in SQL Server 2014.

Per quanto ne so, la modalità riga non ha questa capacità di tabella hash condivisa. Tuttavia, in alcuni casi, in genere con una stima di meno di 100 righe sul lato build, SQL Server creerà una copia separata della tabella hash per ciascun thread (identificabile dal Distribute Streamslead nel join hash). Questo può essere molto potente, ma è molto meno affidabile della modalità batch poiché dipende dalle stime della cardinalità e SQL Server sta cercando di valutare i vantaggi rispetto al costo di creazione di una copia completa della tabella hash per ogni thread.

UNION ALL: un'alternativa più semplice

Paul White ha sottolineato che un'altra opzione, e potenzialmente più semplice, sarebbe quella di utilizzare UNION ALLper combinare le righe per ciascun valore. Questa è probabilmente la tua scommessa migliore supponendo che sia facile per te creare questo SQL in modo dinamico. Per esempio:

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

Ciò produce anche un piano che è in grado di utilizzare la modalità batch e fornisce prestazioni ancora migliori rispetto alla risposta originale. (Anche se in entrambi i casi le prestazioni sono abbastanza veloci che qualsiasi selezione o scrittura dei dati su un tavolo diventa rapidamente il collo di bottiglia.) L' UNION ALLapproccio evita anche di giocare come moltiplicare per 0. A volte è meglio pensare semplice!

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

inserisci qui la descrizione dell'immagine


Il trucco UNION ALL è buono a sapersi, ma fallisce nel caso in cui la query di origine non sia solo una tabella ma una query costosa da sola.
boot4life,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.