Как использовать пакетный режим с UNPIVOT (соединение цикла)?

12

У меня есть запрос следующей формы:

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

Это берет каждую строку из подзапроса, поддерживаемого Columnstore ( ColumnstoreTable), и умножает эти строки. Это по сути UNPIVOT. Реальный запрос больше, чем этот. Эта часть запроса подается в другую обработку.

Проблема здесь заключается в том, что это CROSS APPLYреализовано как соединение цикла, что является разумным выбором. К сожалению, циклические соединения не поддерживают пакетный режим.

Эта часть запроса очень критична для производительности, и я подозреваю, что запуск его в пакетном режиме может быть очень полезным для производительности.

Как я могу переписать этот запрос, чтобы не переходить из пакетного режима?

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

boot4life
источник

Ответы:

12

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

-- 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: более простая альтернатива

Пол Уайт отметил, что другим, и, возможно, более простым вариантом будет использование 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