持久的计算列导致扫描


9

将常规列转换为持久计算列会导致此查询无法执行索引查找。为什么?

在多个SQL Server版本上进行了测试,包括2016 SP1 CU1。

复制

麻烦的是table1col7

表格和查询是原始文档的部分(简化)版本。我知道查询可以用不同的方式重写,并且出于某种原因可以避免该问题,但是我们需要避免触摸代码,而为什么table1找不到原因仍然存在。

正如保罗·怀特(Paul White)所展示的(谢谢!),寻道在强制的情况下仍然可用,所以问题是:为什么寻道未由优化程序选择,以及我们是否可以做一些不同的事情来使寻道按原样进行而不改变?码?

为了澄清有问题的部分,以下是不良执行计划中的相关扫描:

计划

Answers:


12

为什么优化程序未选择搜索


TL:DR扩展的计算列定义会干扰优化器最初对连接进行重新排序的能力。从不同的出发点开始,基于成本的优化会通过优化器采用不同的路径,最终会有不同的最终计划选择。


细节

对于除最简单查询以外的所有查询,优化器都不会尝试探索任何可能的计划之类的东西。取而代之的是,它选择一个看起来合理的起点,然后在一个或多个搜索阶段中花费预算的精力探索逻辑和物理变化,直到找到一个合理的计划。

对于这两种情况,您获得不同计划(最终成本估算不同)的主要原因是起点不同。从不同的地方开始,优化最终在不同的地方(经过有限的探索和实现迭代次数之后)。我希望这是相当直观的。

我提到的起点在某种程度上基于查询的文本表示形式,但是在内部树表示形式经过查询编译的解析,绑定,规范化和简化阶段时,会对它们进行更改。

重要的是,确切的起点在很大程度上取决于优化程序选择的初始连接顺序。在加载统计信息之前以及在得出任何基数估计之前都进行了此选择。但是,已经从系统元数据中获得了每个表中的总基数(行数)。

因此,初始联接顺序基于启发式方法。例如,优化器尝试重写树,以使较小的表先于较大的表联接,而内部联接先于外部联接(和交叉联接)出现。

计算列的存在会干扰此过程,尤其是优化程序将外部联接推入查询树的能力。这是因为在进行联接重新排序之前,已计算的列已扩展为其基础表达式,并且将联接移动通过复杂表达式比将其移动通过简单列引用要困难得多。

涉及的树很大,但为说明起见,非计算列的初始查询树始于:(请注意顶部的两个外部联接)

LogOp_Select
    LogOp_Apply (x_jtLeftOuter)
        LogOp_LeftOuterJoin
            LogOp_NAryJoin
                LogOp_LeftAntiSemiJoin
                    LogOp_NAryJoin
                        LogOp_Get TBL:dbo.table1(别名TBL:a4)
                        LogOp_Select
                            LogOp_Get TBL:dbo.table6(别名TBL:a3)
                            ScaOp_Comp x_cmpEq
                                ScaOp_Identifier QCOL:[a3] .col18
                                ScaOp_Const TI(varchar collat​​e 53256,Var,Trim,ML = 16)
                        LogOp_Select
                            LogOp_Get TBL:dbo.table1(别名TBL:a1)
                            ScaOp_Comp x_cmpEq
                                ScaOp_Identifier QCOL:[a1] .col2
                                ScaOp_Const TI(varchar collat​​e 53256,Var,Trim,ML = 16)
                        LogOp_Select
                            LogOp_Get TBL:dbo.table5(别名TBL:a2)
                            ScaOp_Comp x_cmpEq
                                ScaOp_Identifier QCOL:[a2] .col2
                                ScaOp_Const TI(varchar collat​​e 53256,Var,Trim,ML = 16)
                        ScaOp_Comp x_cmpEq
                            ScaOp_Identifier QCOL:[a4] .col2
                            ScaOp_Identifier QCOL:[a3] .col19
                    LogOp_Select
                        LogOp_Get TBL:dbo.table7(别名TBL:a7)
                        ScaOp_Comp x_cmpEq
                            ScaOp_Identifier QCOL:[a7] .col22
                            ScaOp_Const TI(varchar collat​​e 53256,Var,Trim,ML = 16)
                    ScaOp_Comp x_cmpEq
                        ScaOp_Identifier QCOL:[a4] .col2
                        ScaOp_Identifier QCOL:[a7] .col23
                LogOp_Select
                    LogOp_Get TBL:table1(别名TBL:cdc)
                    ScaOp_Comp x_cmpEq
                        ScaOp_Identifier QCOL:[cdc] .col6
                        ScaOp_Const TI(smallint,ML = 2)XVAR(smallint,不拥有,值= 4)
                LogOp_Get TBL:dbo.table5(别名TBL:a5) 
                LogOp_Get TBL:table2(别名TBL:cdt)  
                ScaOp_Logical x_lopAnd
                    ScaOp_Comp x_cmpEq
                        ScaOp_Identifier QCOL:[a5] .col2
                        ScaOp_Identifier QCOL:[cdc] .col2
                    ScaOp_Comp x_cmpEq
                        ScaOp_Identifier QCOL:[a4] .col2
                        ScaOp_Identifier QCOL:[cdc] .col2
                    ScaOp_Comp x_cmpEq
                        ScaOp_Identifier QCOL:[cdt] .col1
                        ScaOp_Identifier QCOL:[cdc] .col1
            LogOp_Get TBL:table3(别名TBL:ahcr)
            ScaOp_Comp x_cmpEq
                ScaOp_Identifier QCOL:[ahcr] .col9
                ScaOp_Identifier QCOL:[cdt] .col1

计算列查询的相同片段是:(请注意,外部联接的位置要低得多,扩展的计算列定义以及(内部)联接顺序中的其他一些细微差别)

LogOp_Select
    LogOp_Apply (x_jtLeftOuter)
        LogOp_NAryJoin
            LogOp_LeftAntiSemiJoin
                LogOp_NAryJoin
                    LogOp_Get TBL:dbo.table1(别名TBL:a4)
                    LogOp_Select
                        LogOp_Get TBL:dbo.table6(别名TBL:a3)
                        ScaOp_Comp x_cmpEq
                            ScaOp_Identifier QCOL:[a3] .col18
                            ScaOp_Const TI(varchar collat​​e 53256,Var,Trim,ML = 16)
                    LogOp_Select
                        LogOp_Get TBL:dbo.table1(别名TBL:a1
                        ScaOp_Comp x_cmpEq
                            ScaOp_Identifier QCOL:[a1] .col2
                            ScaOp_Const TI(varchar collat​​e 53256,Var,Trim,ML = 16)
                    LogOp_Select
                        LogOp_Get TBL:dbo.table5(别名TBL:a2)
                        ScaOp_Comp x_cmpEq
                            ScaOp_Identifier QCOL:[a2] .col2
                            ScaOp_Const TI(varchar collat​​e 53256,Var,Trim,ML = 16)
                    ScaOp_Comp x_cmpEq
                        ScaOp_Identifier QCOL:[a4] .col2
                        ScaOp_Identifier QCOL:[a3] .col19
                LogOp_Select
                    LogOp_Get TBL:dbo.table7(别名TBL:a7) 
                    ScaOp_Comp x_cmpEq
                        ScaOp_Identifier QCOL:[a7] .col22
                        ScaOp_Const TI(varchar collat​​e 53256,Var,Trim,ML = 16)
                ScaOp_Comp x_cmpEq
                    ScaOp_Identifier QCOL:[a4] .col2
                    ScaOp_Identifier QCOL:[a7] .col23
            LogOp_Project
                LogOp_LeftOuterJoin
                    LogOp_Join
                        LogOp_Select
                            LogOp_Get TBL:table1(别名TBL:cdc) 
                            ScaOp_Comp x_cmpEq
                                ScaOp_Identifier QCOL:[cdc] .col6
                                ScaOp_Const TI(smallint,ML = 2)XVAR(smallint,不拥有,值= 4)
                        LogOp_Get TBL:table2(别名TBL:cdt) 
                        ScaOp_Comp x_cmpEq
                            ScaOp_Identifier QCOL:[cdc] .col1
                            ScaOp_Identifier QCOL:[cdt] .col1
                    LogOp_Get TBL:table3(别名TBL:ahcr) 
                    ScaOp_Comp x_cmpEq
                        ScaOp_Identifier QCOL:[ahcr] .col9
                        ScaOp_Identifier QCOL:[cdt] .col1
                AncOp_PrjList 
                    AncOp_PrjEl QCOL:[cdc] .col7
                        ScaOp_Convert char collat​​e 53256,Null,Trim,ML = 6
                            ScaOp_IIF varchar collat​​e 53256,Null,Var,Trim,ML = 6
                                ScaOp_Comp x_cmpEq
                                    ScaOp_Intrinsic isnumeric
                                        ScaOp_固有权利
                                            ScaOp_Identifier QCOL:[cdc] .col4
                                            ScaOp_Const TI(int,ML = 4)XVAR(int,不拥有,值= 4)
                                    ScaOp_Const TI(int,ML = 4)XVAR(int,不拥有,值= 0)
                                ScaOp_Const TI(varchar collat​​e 53256,Var,Trim,ML = 1)XVAR(varchar,Owned,Value = Len,Data =(0,))
                                ScaOp_Intrinsic子字符串
                                    ScaOp_Const TI(int,ML = 4)XVAR(int,不拥有,值= 6)
                                    ScaOp_Const TI(int,ML = 4)XVAR(int,不拥有,值= 1)
                                    ScaOp_Identifier QCOL:[cdc] .col4
            LogOp_Get TBL:dbo.table5(别名TBL:a5)
            ScaOp_Logical x_lopAnd
                ScaOp_Comp x_cmpEq
                    ScaOp_Identifier QCOL:[a5] .col2
                    ScaOp_Identifier QCOL:[cdc] .col2
                ScaOp_Comp x_cmpEq
                    ScaOp_Identifier QCOL:[a4] .col2
                    ScaOp_Identifier QCOL:[cdc] .col2

设置初始连接顺序后,将加载统计信息并在树上执行初始基数估计。具有不同顺序的联接也会影响这些估计,因此在以后的基于成本的优化过程中也会产生连锁反应。

最后,对于本部分,在树的中间插入外部联接可以防止在基于成本的优化过程中进一步进行联接重新排序规则匹配。


使用计划指南(或等价一个USE PLAN提示- 如您所查询的改变搜索策略,以更加面向目标的方法,引导由一般的形状和所提供的模板的功能。这解释了为什么在使用计划指南或提示时,优化器可以table1针对计算的和非计算的列模式找到相同的查找计划。

我们是否可以做一些不同的事情以使寻求实现

这是您仅需担心的问题,即优化器本身是否找不到具有可接受的性能特征的计划。

所有常规的调整工具都可能适用。例如,您可以将查询分解为更简单的部分,查看并改进可用的索引编制,更新或创建新的统计信息等等。

所有这些都会影响基数估计,通过优化器采用的代码路径,并以微妙的方式影响基于成本的决策。

您最终可能会使用提示(或计划指南),但这通常不是理想的解决方案。


评论中的其他问题

我同意最好简化查询等操作,但是有没有办法(跟踪标志)使优化器继续优化并达到相同的结果呢?

不,没有跟踪标志可以执行详尽的搜索,而且您也不想这样做。可能的搜索空间是巨大的,并且超过宇宙年龄的编译时间将不会被很好地接受。同样,优化器也不知道每一种可能的逻辑变换(没人知道)。

另外,为什么在保留该列时需要进行复杂的扩展?为什么优化器无法避免对其进行扩展,将其视为常规列并达到相同的起点?

计算列被扩展(如视图一样)以启用其他优化机会。扩展可以在稍后的过程中与例如持久化的列或索引相匹配,但这是在固定初始连接顺序之后发生的。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.