为什么优化程序未选择搜索
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 collate 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 collate 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 collate 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 collate 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 collate 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 collate 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 collate 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 collate 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 collate 53256,Null,Trim,ML = 6
ScaOp_IIF varchar collate 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 collate 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
针对计算的和非计算的列模式找到相同的查找计划。
我们是否可以做一些不同的事情以使寻求实现
这是您仅需担心的问题,即优化器本身是否找不到具有可接受的性能特征的计划。
所有常规的调整工具都可能适用。例如,您可以将查询分解为更简单的部分,查看并改进可用的索引编制,更新或创建新的统计信息等等。
所有这些都会影响基数估计,通过优化器采用的代码路径,并以微妙的方式影响基于成本的决策。
您最终可能会使用提示(或计划指南),但这通常不是理想的解决方案。
评论中的其他问题
我同意最好简化查询等操作,但是有没有办法(跟踪标志)使优化器继续优化并达到相同的结果呢?
不,没有跟踪标志可以执行详尽的搜索,而且您也不想这样做。可能的搜索空间是巨大的,并且超过宇宙年龄的编译时间将不会被很好地接受。同样,优化器也不知道每一种可能的逻辑变换(没人知道)。
另外,为什么在保留该列时需要进行复杂的扩展?为什么优化器无法避免对其进行扩展,将其视为常规列并达到相同的起点?
计算列被扩展(如视图一样)以启用其他优化机会。扩展可以在稍后的过程中与例如持久化的列或索引相匹配,但这是在固定初始连接顺序之后发生的。