如何在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,但这并没有改变没有哈希连接的相等连接条件的事实。

Answers:


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语句替换了SUMCASE语句,以避免必须将所有这些行流式传输到控制台,然后在我所躺着的真正的100MM行列存储表上运行查询,那么我看到生成必要的300MM的性能相当不错行数:

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
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.