在准备我之前的Constant Scan 问题时,我VALUES
以各种方式进行了试验,并遇到了关于加入的事情,VALUES
这对我来说很奇怪。
设置很简单
CREATE TABLE #data ([Id] int);
INSERT INTO #data VALUES (101), (103);
然后有一个查询
DECLARE @id1 int = 101, @id2 int = 102;
SELECT *
FROM (VALUES (@id1), (@id2)) p([Id])
FULL HASH JOIN #data d ON d.[Id] = p.[Id];
没有什么特别的。如果您运行它,它会工作并产生结果。这是它的执行计划
VALUES
但是从中删除一行
SELECT *
FROM (VALUES (@id1)) p([Id])
FULL HASH JOIN #data d ON d.[Id] = p.[Id];
导致优化器失败
消息 8622,级别 16,状态 1,第 1 行
查询处理器无法生成查询计划...
为什么?有没有办法(除了将参数放入临时表之外)使用哈希算法使其工作?
注意:这不是真正的工具,而是用于研究优化器行为和功能的目的。
上面的例子是在
Microsoft SQL Server 2017 (RTM-CU15-GDR) (KB4505225) - 14.0.3192.2 (X64)
简而言之。因为
HASH
对优化器的腿进行提示射击,而优化器本身对另一条腿进行射击。被射向两个优化器都无法越过终点线。为了更好地说明发生了什么,让我们重写有问题的查询以连接两个
VALUES
并改用合并算法这个查询的执行计划很简单。有带有两个 Constant Scan 输入的 Merge Join 运算符。
这两个常量扫描与优化器不同。
表示单行输入的一个列名称的前缀为
Expr
,而另一个表示多行输入的列名称的前缀为Union
。来自多行 Constant Scan 的数据在 Merge Join 谓词中以一种“按引用”([Union1001]
) 的方式访问,而单行 Constant Scan 数据以一种“按值”的方式访问(请参阅@id1
被替换而不是[Expr1000]
)。这种“按引用”→“按值”替换是在早期优化阶段执行的标量映射的结果。
可以看到(使用跟踪标志 8606)在输入树连接谓词中是
[Union1001] = [Expr1000]
但随后在简化树中它变成了
[Union1001] = @id1
标量映射是投影拉动逻辑的一部分,实际上在进入简化阶段之前执行。
前面可能有人注意到,Merge Join 节点只有剩余谓词,没有连接相等谓词。这是因为连接相等谓词已被标量映射消除。is 相等谓词,
[Union1001] = @id1
但不能作为连接相等谓词。为此,它必须从两个输入中引用列,但@id1
它是可变的而不是列。因此,
ON d.[Id] = p.[Id]
最初是 equijoin,查询转换为非 equijoin(这是一种特殊情况,因此,顺便说一下,优化器没有在 Merge Join 下面为未排序的 Constant Scan 输入引入排序)。幸运的是,在合并算法的情况下,优化器有这样的非等值连接替代方案。在使用散列算法的情况下,非等值连接替代方案不存在,因此,连接相等谓词消除会导致优化器稍后失败。
没有阻止标量映射的跟踪标志(*),无论是查询级别、会话级别还是启动。并且没有可以关闭以防止它的优化规则,因为它不是由规则执行的。
我只能通过在
COptExpr::PexprMapScalar
例程中设置断点来执行有问题的查询eax
并在调用后修改寄存器的值,ScaOp_Identifier::ClassNo
使 SQL Server 认为第二个操作数ScaOp_Comp
不是标识符。这是问题中发布的有问题查询的简化树
这是它的计划。
它实际上没有什么意义,因为获得的计划成本是 0.0210675 个单位,而在没有
HASH
提示的情况下运行查询会导致使用 Merge Join 的执行计划(再次注意 Merge Join 下面没有排序)成本为 0.0088948 个单位。
(*)可能存在跟踪标志的组合。我认为不是,但我没有探索所有代码路径,所以我不确定。