恒定扫描加入

i-o*_*one 5 sql-server optimization database-internals

在准备我之前的Constant Scan 问题时,我VALUES以各种方式进行了试验,并遇到了关于连接的事情,VALUES这对我来说很奇怪。

设置很简单

CREATE TABLE #data ([Id] int);
INSERT INTO #data VALUES (101), (103);
Run Code Online (Sandbox Code Playgroud)

然后有一个查询

DECLARE @id1 int = 101, @id2 int = 102;

SELECT *
FROM (VALUES (@id1), (@id2)) p([Id])
    FULL HASH JOIN #data d ON d.[Id] = p.[Id];
Run Code Online (Sandbox Code Playgroud)

没有什么特别之处。如果你运行它,它会工作并产生它的结果。这是它的执行计划

恒扫描加盟计划

VALUES然而删除行

SELECT *
FROM (VALUES (@id1)) p([Id])
    FULL HASH JOIN #data d ON d.[Id] = p.[Id];
Run Code Online (Sandbox Code Playgroud)

导致优化器失败

消息 8622,级别 16,状态 1,第 1 行
查询处理器无法生成查询计划...

为什么?有没有办法(除了将参数放入临时表)使用哈希算法使其工作?

注意:这不是真正的设备,用于研究优化器行为和功能。


上面的例子在

Microsoft SQL Server 2017 (RTM-CU15-GDR) (KB4505225) - 14.0.3192.2 (X64)

i-o*_*one 7

为什么?

简而言之。因为HASH优化器的腿和优化器本身的镜头对另一个的提示镜头。被两个优化器击中不能越过终点线。


为了更好地说明发生了什么,让我们重写有问题的查询以连接两个VALUES并使用合并算法

DECLARE @id1 int = 101, @id3 int = 103;

SELECT *
FROM (VALUES (@id1)) p([Id])
    FULL MERGE JOIN (VALUES (@id1), (@id3)) d([Id]) ON d.[Id] = p.[Id];
Run Code Online (Sandbox Code Playgroud)

这个查询的执行计划很简单。有带有两个 Constant Scan 输入的 Merge Join 运算符。

执行计划

不过,这两个常量扫描与优化器不同。

执行计划属性

一个代表单行输入的列名以 为前缀Expr,而另一个代表多行输入的列名以 为前缀Union。来自多行常量扫描的数据在 Merge Join 谓词中被访问为一种“按引用”([Union1001]),而单行常量扫描数据被访问为一种“按值”(参见@id1被替换而不是[Expr1000])。

这种“按引用”?“按值”替换是在早期优化阶段执行的标量映射的结果。

可以看到(使用跟踪标志 8606)在输入树连接谓词中是 [Union1001] = [Expr1000]

*** 输入树:***
    ...
    LogOp_FullOuterJoin
        ...
        ScaOp_Comp x_cmpEq
            ScaOp_Identifier COL:Union1001
            ScaOp_Identifier COL:Expr1000
    ...

但是在简化树中它变成了 [Union1001] = @id1

*** 简化树:***
    LogOp_FullOuterJoin
        ...
        ScaOp_Comp x_cmpEq
            ScaOp_Identifier COL:Union1001
            ScaOp_Identifier COL:@id1

标量映射是投影拉动逻辑的一部分,进入简化阶段之前实际执行。

之前可能已经注意到,Merge Join 节点只有残差谓词,而没有连接等式谓词。这是因为连接相等谓词已被标量映射消除。该[Union1001] = @id1是平等的谓词,但它不能作为一个加入相等谓词。为此,它必须引用来自两个输入的列,但它@id1是可变的而不是列。

因此,ON d.[Id] = p.[Id]最初是 equijoin ,查询转换为非 equijoin(这是特殊情况,因此,顺便说一下,优化器没有为非排序的常量扫描输入引入合并连接下面的排序)。幸运的是,在合并算法优化器的情况下,有这样的非等连接替代方案。

在使用散列算法的情况下,非等值连接替代方案不存在,因此,连接等式谓词消除会导致优化器稍后失败。


有没有办法(除了将参数放入临时表)使用哈希算法使其工作?

没有阻止标量映射的跟踪标志(*),无论是查询杠杆、会话级别还是启动。并且没有可以关闭的优化规则来阻止它,因为它不是由规则执行的。

我只能通过在COptExpr::PexprMapScalar例程中设置断点来执行有问题的查询

在此处输入图片说明

eax在调用后修改寄存器的值,ScaOp_Identifier::ClassNo使 SQL Server 认为的第二个操作数ScaOp_Comp不是标识符。

这是问题中发布的有问题的查询的简化树

*** 简化树:***
    LogOp_FullOuterJoin
        LogOp_ConstTableGet (1) COL:Expr1000 
            ScaOp_Identifier COL:@id1 
        LogOp_Get TBL:#data(别名 TBL:d)
        ScaOp_Comp x_cmpEq
            ScaOp_Identifier QCOL: [d].Id
            ScaOp_Identifier COL:Expr1000
*******************

这里的计划获得。

实际上没有什么意义,因为获得的计划成本是0.0210675个单位,而在没有HASH提示的情况下运行查询会导致执行计划带有Merge Join(注意再次Merge Join下面没有排序)

执行计划没有提示

成本为 0.0088948 单位。


(*)可能存在跟踪标志的组合。我认为它没有,但我没有探索所有代码路径,所以我不确定。