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)
为什么?
简而言之。因为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 单位。
(*)有可能存在跟踪标志的组合。我认为它没有,但我没有探索所有代码路径,所以我不确定。
| 归档时间: |
|
| 查看次数: |
381 次 |
| 最近记录: |