boo*_*ife 12 sql-server columnstore unpivot sql-server-2014 batch-mode
我有以下形式的查询:
SELECT ...
FROM ColumnstoreTable cs
CROSS APPLY (
SELECT *
FROM (VALUES
('A', cs.DataA)
, ('B', cs.DataB)
, ('C', cs.DataC)
) x(Col0, Col1)
) someValues
Run Code Online (Sandbox Code Playgroud)
这从 Columnstore 支持的子查询 ( ColumnstoreTable
) 中获取每一行并将这些行相乘。这本质上是一个UNPIVOT
. 真正的查询比这更大。这部分查询会进入其他处理。
这里的问题是这CROSS APPLY
是作为一个循环连接实现的,这是一个合理的选择。不幸的是,循环连接不支持批处理模式。
这部分查询对性能非常关键,我怀疑以批处理模式运行它可能对性能非常有益。
我怎样才能重写这个查询,这样我就不会退出批处理模式?
我确实尝试使用临时表而不是VALUES
,但这并没有改变这样一个事实,即没有相等连接条件来进行哈希连接。
Geo*_*son 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
Run Code Online (Sandbox Code Playgroud)
性能和查询计划
这种方法产生如下查询计划,并且哈希匹配以批处理模式执行:
如果我更换了SELECT
一个声明SUM
中的CASE
为了语句具有传输所有这些行控制台,然后运行上我已经躺在附近一个真正的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:更简单的选择
Paul White 指出另一个可能更简单的选项是使用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
Run Code Online (Sandbox Code Playgroud)
这也产生了一个能够利用批处理模式并提供比原始答案更好的性能的计划。(尽管在这两种情况下,性能都足够快,以至于任何选择或将数据写入表都会很快成为瓶颈。)该UNION ALL
方法还避免玩乘以 0 之类的游戏。有时最好考虑简单!
CPU time = 8673 ms, elapsed time = 4270 ms.