当构建端为空时,SQL Server 为什么/何时评估内部散列连接的探测端?

Mar*_*ith 11 sql-server parallelism execution-plan sql-server-2017

设置

DROP TABLE IF EXISTS #EmptyTable, #BigTable

CREATE TABLE #EmptyTable(A int);
CREATE TABLE #BigTable(A int);

INSERT INTO #BigTable
SELECT TOP 10000000 CRYPT_GEN_RANDOM(3)
FROM   sys.all_objects o1,
       sys.all_objects o2,
       sys.all_objects o3;
Run Code Online (Sandbox Code Playgroud)

询问

WITH agg
     AS (SELECT DISTINCT a
         FROM   #BigTable)
SELECT *
FROM   #EmptyTable E
       INNER HASH JOIN agg B
                    ON B.A = E.A;
Run Code Online (Sandbox Code Playgroud)

执行计划

在此处输入图片说明

问题

这是我今天之前没有注意到的现象的简化再现。我对内部散列连接的期望是,如果构建输入为空,则不应执行探测端,因为连接不会返回任何行。上面的示例与此相反,并从表中读取了 1000 万行。这使查询的执行时间增加了 2.196 秒 (99.9%)。

其他观察

  1. 使用OPTION (MAXDOP 1)执行计划从#BigTable. 该ActualExecutions0对哈希连接内所有的运营商。
  2. 对于查询SELECT * FROM #EmptyTable E INNER HASH JOIN #BigTable B ON B.A = E.A- 我得到一个并行计划,散列连接内部的扫描运算符确实具有ActualExecutionsDOP,但仍然没有读取任何行。该计划没有重新分区流操作符(或聚合)

这里发生了什么?为什么原计划会出现问题而其他情况没有?

Pau*_*ite 10

当构建为空时不运行连接的探测端是一种优化。当探测端有一个子分支时,即当有一个交换操作符时,它不适用于并行行模式散列连接

多年前,Adam Machanic 在现已不存在的 Connect 反馈网站上有一份类似的报告。场景是探针端的一个启动过滤器,它意外地运行了它的子操作符。微软的回答是引擎需要保证某些结构被初始化,唯一合理的强制执行方法是确保探针端操作符是打开的。

我自己对细节的回忆是,不初始化子树会导致难以修复的并行计时错误。确保子分支启动是解决这些问题的方法。

批处理模式散列连接没有这种副作用,因为管理线程的方式不同。

在您的特定情况下,效果更明显,因为哈希聚合正在阻塞;它在迭代器的 Open() 调用期间消耗其全部输入。当探测端只有流操作符时,性能影响通常会更加有限,这取决于将第一行返回到散列连接的探测端所需的工作量。