Як використати пакетний режим за допомогою UNPIVOT (з'єднання циклу)?


12

У мене є запит такої форми:

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

Це займає кожен рядок із підзапиту, підтримуваного стовпцем ( ColumnstoreTable), і множує ці рядки. Це по суті UNPIVOT. Справжній запит більший за цей. Ця частина запиту подається в іншу обробку.

Проблема тут полягає в тому, що це CROSS APPLYреалізується як з'єднання циклу, що є розумним вибором. На жаль, приєднання циклу не підтримує пакетний режим.

Ця частина запиту є дуже важливою для продуктивності, і я підозрюю, що його виконання в пакетному режимі може бути дуже корисним для продуктивності.

Як я можу переписати цей запит, щоб не переходити з пакетного режиму?

Я намагався використовувати тимчасову таблицю замість VALUES, але це не змінило факту відсутності умови об'єднання рівності для хеш-з'єднання.

Відповіді:


12

Одним із підходів може бути використання таблиці #temp для значень, а також введення стовпчика фіктивного еджійона, щоб дозволити хеш-з'єднання. Наприклад:

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

План продуктивності та запиту

Цей підхід дає план запитів на зразок наступного, і хеш-відповідність виконується в пакетному режимі:

введіть тут опис зображення

Якщо я заміню SELECTоператор SUMна CASEоператор, щоб уникнути необхідності передавати всі ці рядки до консолі, а потім запустити запит у реальну таблицю стовпців стовпців 100 ММ, у якій я лежу, я бачу досить хороші показники для створення необхідних 300 мм рядки:

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

І власне план показує хорошу паралелізацію хеш-з'єднання.

введіть тут опис зображення

Примітки про паралелізацію хеш-з'єднання, коли всі рядки мають однакове значення

Виконання цього запиту сильно залежить від кожного потоку на стороні зонда з'єднання, що має доступ до повної хеш-таблиці (на відміну від хеш-розділеної версії, яка відображатиме всі рядки в один потік, враховуючи, що існує лише одне чітке значення для dummyстовпчика).

На щастя, це вірно в цьому випадку (як ми бачимо за відсутністю Parallelismоператора на стороні зонда) і надійно повинно бути правдою, оскільки пакетний режим створює єдину хеш-таблицю, яка поділяється по потоках. Таким чином, кожен потік може взяти свої рядки з Columnstore Index Scanта зіставити їх із загальною таблицею хешу. У SQL Server 2012 ця функціональність була набагато менш передбачуваною, оскільки розлив призвів до того, що оператор перезапустився в рядковому режимі, втрачаючи при цьому перевагу пакетного режиму, а також вимагаючи від Repartition Streamsоператора з боку зонда з'єднання, що може спричинити перекос потоку в цьому випадку . Дозвіл розливів залишається в пакетному режимі - це головне вдосконалення для SQL Server 2014.

Наскільки мені відомо, рядовий режим не має такої можливості спільної хеш-таблиці. Однак у деяких випадках, як правило, з оцінкою менше 100 рядків на стороні збірки, SQL Server створить окрему копію хеш-таблиці для кожного потоку (ідентифікується Distribute Streamsведучим у хеш-з'єднанні). Це може бути дуже потужним, але набагато менш надійним, ніж пакетний режим, оскільки це залежить від ваших оцінок кардинальності, і SQL Server намагається оцінити переваги порівняно з витратами на створення повної копії хеш-таблиці для кожного потоку.

СОЮЗ ВСІ: простіша альтернатива

Пол Вайт зазначив, що ще одним, і потенційно простішим, варіантом буде використання UNION ALLдля об'єднання рядків для кожного значення. Це, швидше за все, найкраща ставка, припускаючи, що вам легко створити цей SQL динамічно. Наприклад:

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

Це також дає план, який здатний використовувати пакетний режим і забезпечує навіть кращу продуктивність, ніж вихідна відповідь. (Хоча в обох випадках продуктивність досить швидка, що будь-який вибір або запис даних у таблицю швидко перетворюється на вузьке місце.) UNION ALLПідхід також дозволяє уникнути гри в такі ігри, як множення на 0. Іноді найкраще думати просто!

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

введіть тут опис зображення


Трюк UNION ALL добре знати, але не вдається, якщо вихідний запит не є просто таблицею, а дорогим запитом сам по собі.
boot4life
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.