恒定扫描应用

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

这个问题是关于VALUES这里这里开始的构造的优化器行为探索的延续。我想问一下VALUESAPPLY这次。

使用CROSS APPLY别名作为需要在查询的各个部分中引用的表达式是常见的模式。例如:

CREATE TABLE #data (N int);
INSERT INTO #data VALUES (5), (4), (3), (2), (1);

SELECT d.N, c.[Square]
FROM #data d
    CROSS APPLY (VALUES (d.N * d.N)) c([Square])
WHERE c.[Square] BETWEEN 1 AND 10
ORDER BY c.[Square];
Run Code Online (Sandbox Code Playgroud)

我自己总是CROSS APPLY在这种情况下使用,但有时我会遇到包裹在 inline-TVF 和OUTER APPLY-ed 中的此类表达式。因此,出于好奇,我换CROSSOUTER的exampling查询

SELECT d.N, c.[Square]
FROM #data d
    OUTER APPLY (VALUES (d.N * d.N)) c([Square])
WHERE c.[Square] BETWEEN 1 AND 10
ORDER BY c.[Square];
Run Code Online (Sandbox Code Playgroud)

检查它是否重要。

据我了解,在这种情况下,CROSS APPLY和 的语义OUTER APPLY是相等的,因为VALUES (d.N * d.N)对于d. 我的期望是优化器会为两者生成相同的执行计划(同样它有时会将 left join 更改为 inner )。然而,情况并非如此。

OUTER变形具有一个执行计划类似其书面形式。它已APPLY实现为嵌套循环,其表达式在内侧以常量扫描计算。然后过滤器是用来解决WHERE谓词的,而排序是用来解决ORDER BY最终的。

外申请计划

CROSS修改的执行计划看起来好像根本没有APPLY,并且表达式已被替换而不是其别名。它已将WHERE谓词推送到表扫描。然后是 Compute Scalar 执行表达式计算,最后也是 Sort。

交叉申请计划

为什么优化器不想APPLY在 的情况下摆脱OUTER,就像它所做的那样CROSS?它掌握了所有信息,可以得出结论VALUES每个表格行恰好生成一行。

我知道答案可能很简短,例如“理论上可以,但这就是当前的实现”。因此,为了启发如何构建更彻底的答案,我有关于这两个查询的优化路径的其他问题(一路走来)。

在问题的其余部分,使用缩短的查询:

SELECT d.N, c.[Square]
FROM #data d
    CROSS APPLY (VALUES (d.N * d.N)) c([Square]);

SELECT d.N, c.[Square]
FROM #data d
    OUTER APPLY (VALUES (d.N * d.N)) c([Square]);
Run Code Online (Sandbox Code Playgroud)

他们缺乏WHEREORDER BY。尽管如此,它们具有所有必要的属性,并且也有不同的执行计划。

交叉申请

让我们从输入树(跟踪标志 8606)开始。此时,逻辑树与查询的书面形式很好地对应。

*** Input Tree: ***
LogOp_Project QCOL: [d].N COL: Expr1002 
    LogOp_Apply (x_jtInner)
        LogOp_Get TBL: #data(alias TBL: d)
        LogOp_Project
            LogOp_ConstTableGet (1) [empty]
            AncOp_PrjList 
                AncOp_PrjEl COL: Expr1002 
                    ScaOp_Arithmetic x_aopMult
                        ScaOp_Identifier QCOL: [d].N
                        ScaOp_Identifier QCOL: [d].N
    AncOp_PrjList 
*******************
Run Code Online (Sandbox Code Playgroud)

然后,在进入简化阶段之前,会发生一个名为投影拉动的事件。优化器将标量表达式计算拉到应用之上

*** Before Simplification Tree (not visible with TF 8606) ***
LogOp_Project QCOL: [d].N COL: Expr1002 
    LogOp_Apply (x_jtInner)
        LogOp_Get TBL: #data(alias TBL: d)
        LogOp_ConstTableGet (1) [empty]
    AncOp_PrjList 
        AncOp_PrjEl COL: Expr1002 
            ScaOp_Arithmetic x_aopMult
                ScaOp_Identifier QCOL: [d].N
                ScaOp_Identifier QCOL: [d].N
*******************
Run Code Online (Sandbox Code Playgroud)

在Apply上拉标量表达式的目的是什么?自然抑制拉动的东西有哪些,有没有办法强行抑制?

在该简化阶段开始之后,在应用的第一个规则(跟踪标志 8621)中PrjApplyHandler更改 Apply to Join

***** Rule applied: Prj APPLY stack -> Prj Join stack
LogOp_Project
    LogOp_Join
        LogOp_Get TBL: #data(alias TBL: d)
        LogOp_ConstTableGet (1) [empty]
        ScaOp_Const TI(bit,ML=1) XVAR(bit,Not Owned,Value=1)
    AncOp_PrjList 
        ...
Run Code Online (Sandbox Code Playgroud)

如果在此阶段实施,查询的执行计划将如下所示:

CROSS APPLY 标量拉

然后SimplifyJoinWithCTG规则开始行动。它删除交叉连接到空的单行,LogOp_ConstTableGet留下LogOp_Get唯一的

***** Rule applied: Join/LSJ(ConstTableGet(1),x0,True) --> x0
    LogOp_Get TBL: #data(alias TBL: d)
Run Code Online (Sandbox Code Playgroud)

所以,经过所有的简化,逻辑树变成了

*** Simplified Tree: ***
LogOp_Project
    LogOp_Get TBL: #data(alias TBL: d)
    AncOp_PrjList 
        AncOp_PrjEl COL: Expr1002 
            ScaOp_Arithmetic x_aopMult
                ScaOp_Identifier QCOL: [d].N
                ScaOp_Identifier QCOL: [d].N
*******************
Run Code Online (Sandbox Code Playgroud)

然后它流动,不变,直到被简单地实现为

交叉申请决赛

禁用SimplifyJoinWithCTG规则会导致执行计划将标量表达式计算推到连接下方,但推到其外侧

CROSS APPLY 标量推送

(此计划也已JoinCommute禁用)。

在之前将标量表达式拉到上方之后,将其推到连接下方的目的是什么?

外敷

用于OUTER APPLY修改的输入树几乎与CROSS APPLY. 唯一的区别是LogOp_Applyx_jtLeftOuter类型

*** Input Tree: ***
LogOp_Project QCOL: [d].N COL: Expr1002 
    LogOp_Apply (x_jtLeftOuter)
        LogOp_Get TBL: #data(alias TBL: d)
        LogOp_Project
            LogOp_ConstTableGet (1) [empty]
            AncOp_PrjList 
                AncOp_PrjEl COL: Expr1002 
                    ScaOp_Arithmetic x_aopMult
                        ScaOp_Identifier QCOL: [d].N
                        ScaOp_Identifier QCOL: [d].N
    AncOp_PrjList 
*******************

从这一点开始,情况就大不相同了OUTER APPLY。第一个区别是在这种情况下标量表达式不会发生投影牵引。为什么优化器不为 提取标量表达式OUTER APPLY

第二个区别是优化器在简化阶段不会改变 Apply to Join。实际上,即使在输出树(跟踪标志 8607)中,它也显示为PhyOp_Apply,而 join 仅显示为优化后。

尽管如此,RedundantApplyOJApplyHandler应用了简化规则

RedundantApplyOJ 已应用 ApplyHandler 已应用

简化树中没有它的迹象

*** Simplified Tree: ***
LogOp_Apply (x_jtLeftOuter)
    LogOp_Get TBL: #data(alias TBL: d)
    LogOp_ConstTableGet (1) COL: Expr1002 
        ScaOp_Arithmetic x_aopMult
            ScaOp_Identifier QCOL: [d].N
            ScaOp_Identifier QCOL: [d].N
*******************
Run Code Online (Sandbox Code Playgroud)

此外,SimplifyJoinWithCTG甚至不考虑规则OUTER APPLY(意味着它没有机会匹配或不匹配)。

然后,在完成所有简化之后,逻辑树将保持不变,直到在快速计划阶段实施为

外部应用最终

为什么优化器不尝试在简化阶段更改 Apply to JoinOUTER APPLY就像它所做的那样CROSS APPLY?在这种情况下,在简化过程中做什么RedundantApplyOJApplyHandler规则?它们是类似于A*1=A(意味着没有任何有效改变),或者它们只是改变逻辑树节点的某些属性而不影响树形?

pac*_*ely 1

在查询运行之前,优化器\xe2\x80\x99 并不知道数据是什么。所以它必须做好一切准备。

\n

如果您使查询更简单并在 #data 表中放入 10 行,则 OUTER APPLY 查询的 FILTER 猜测值为 #data 表行计数的 50% 会变得更加清楚。

\n

OUTER APPLY c.[Square] 结果集的结果显得不确定。

\n

除非可以安全地执行其他操作,否则 APPLY 将始终使用嵌套循环。在这种情况下,\xe2\x80\x99t 不安全,因为内容未知,并且 OUTER APPLY 可能会为 c.[Square] 生成 NULL 结果。

\n

即使您将 #data 设置为 (N int NOT NULL),嵌套循环仍然会被使用,并且估计值也是错误的,因为它\xe2\x80\x99s 外部应用对于优化器来说是不可见的。

\n

注意:c.[Square] 上的任何过滤器可能不对 NULL 敏感:\xe2\x80\x9cNULL <> 5\xe2\x80\x9d 可能会产生不需要的结果。

\n

Here\xe2\x80\x99s 是一个 #data 包含 10 个整数的示例。

\n

优化器估计将从 OUTER APPLY 返回 5 行

\n
IF OBJECT_ID('tempdb..#data') IS NOT NULL DROP TABLE #data\nCREATE TABLE #data (N int);\n\nINSERT INTO #data\nSELECT TOP 10 ROW_NUMBER() OVER (PARTITION BY NULL ORDER BY Object_id) FROM sys.columns\n\nSELECT d.N, c.[Square]\nFROM #data d\n    CROSS APPLY (VALUES (d.N)) c([Square])\nWHERE c.[Square] <> 5;\n\nSELECT d.N, c.[Square]\nFROM #data d\n    OUTER APPLY (VALUES (d.N)) c([Square])\nWHERE c.[Square] <> 5;\n
Run Code Online (Sandbox Code Playgroud)\n

希望有人对 OUTER APPLY 内部结构有更多了解,可以解释为什么优化器会进行盲目猜测......

\n