Bagaimana cara menggunakan Batch Mode dengan UNPIVOT (loop bergabung)?

12

Saya memiliki pertanyaan dari formulir berikut:

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

Ini mengambil setiap baris dari subquery yang didukung Columnstore ( ColumnstoreTable) dan mengalikan baris tersebut. Ini pada dasarnya adalah UNPIVOT. Permintaan sebenarnya lebih besar dari ini. Bagian dari kueri ini dimasukkan ke pemrosesan lain.

Masalahnya di sini adalah bahwa ini CROSS APPLYditerapkan sebagai loop bergabung yang merupakan pilihan yang masuk akal. Sayangnya, loop bergabung tidak mendukung mode batch.

Bagian dari permintaan ini sangat kritis terhadap kinerja dan saya menduga menjalankannya dalam mode batch bisa sangat bermanfaat bagi kinerja.

Bagaimana saya bisa menulis ulang kueri ini sehingga saya tidak beralih dari mode batch?

Saya memang mencoba menggunakan tabel sementara alih-alih VALUES, tapi itu tidak mengubah fakta bahwa tidak ada syarat untuk bergabung bersama hash bergabung.

boot4life
sumber

Jawaban:

12

Salah satu pendekatan mungkin menggunakan tabel #temp untuk nilai-nilai dan juga memperkenalkan kolom equijoin dummy untuk memungkinkan hash bergabung. Sebagai contoh:

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

Rencana kinerja dan kueri

Pendekatan ini menghasilkan rencana kueri seperti berikut, dan pencocokan hash dilakukan dalam mode batch:

masukkan deskripsi gambar di sini

Jika saya mengganti SELECTpernyataan dengan SUMsatu CASEpernyataan untuk menghindari harus melakukan streaming semua baris yang ke konsol dan kemudian menjalankan query pada 100mm nyata baris tabel columnstore saya telah tergeletak di sekitar, saya melihat kinerja yang cukup baik untuk menghasilkan 300MM diperlukan baris:

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

Dan rencana aktual menunjukkan paralelisasi yang baik dari hash join

masukkan deskripsi gambar di sini

Catatan tentang hash bergabung dengan paralelisasi ketika semua baris memiliki nilai yang sama

Kinerja kueri ini sangat bergantung pada setiap utas di sisi probe join yang memiliki akses ke tabel hash lengkap (sebagai lawan dari versi hash yang dipartisi, yang akan memetakan semua baris ke utas tunggal mengingat hanya ada satu nilai berbeda untuk dummykolom).

Untungnya, ini benar dalam kasus ini (seperti yang dapat kita lihat dengan kurangnya Parallelismoperator di sisi probe) dan seharusnya benar karena mode batch membangun tabel hash tunggal yang dibagi di seluruh thread. Oleh karena itu, setiap utas dapat mengambil baris mereka dari Columnstore Index Scandan mencocokkannya dengan tabel hash bersama itu. Dalam SQL Server 2012, fungsi ini jauh lebih mudah diprediksi karena tumpahan menyebabkan operator memulai kembali dalam mode Row, keduanya kehilangan manfaat dari mode batch dan juga membutuhkan Repartition Streamsoperator di sisi probe bergabung yang akan menyebabkan kemiringan benang dalam kasus ini . Mengizinkan tumpahan tetap dalam mode batch adalah peningkatan besar dalam SQL Server 2014.

Setahu saya, mode baris tidak memiliki kemampuan tabel hash bersama ini. Namun, dalam beberapa kasus, biasanya dengan perkiraan kurang dari 100 baris di sisi build, SQL Server akan membuat salinan terpisah dari tabel hash untuk setiap utas (dapat diidentifikasi oleh Distribute Streamspemimpin ke dalam gabungan hash). Ini bisa sangat kuat, tetapi jauh lebih tidak dapat diandalkan daripada mode batch karena itu tergantung pada perkiraan kardinalitas Anda dan SQL Server sedang mencoba untuk mengevaluasi manfaat versus biaya membangun salinan penuh tabel hash untuk setiap utas.

UNION ALL: alternatif yang lebih sederhana

Paul White menunjukkan bahwa opsi lain, dan mungkin lebih sederhana, akan digunakan UNION ALLuntuk menggabungkan baris untuk setiap nilai. Ini kemungkinan adalah taruhan terbaik Anda dengan asumsi bahwa mudah bagi Anda untuk membangun SQL ini secara dinamis. Sebagai contoh:

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

Ini juga menghasilkan rencana yang dapat memanfaatkan mode batch dan memberikan kinerja yang lebih baik daripada jawaban aslinya. (Meskipun dalam kedua kasus kinerjanya cukup cepat sehingga setiap pemilihan atau penulisan data ke meja dengan cepat menjadi hambatan.) UNION ALLPendekatan ini juga menghindari bermain game seperti mengalikan dengan 0. Terkadang lebih baik berpikir sederhana!

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

masukkan deskripsi gambar di sini

Geoff Patterson
sumber
Trik UNION ALL baik untuk diketahui, tetapi gagal jika kueri sumber bukan hanya tabel tetapi kueri yang mahal dengan sendirinya.
boot4life