将常规列转换为持久计算列会导致此查询无法执行索引查找。为什么?
在多个 SQL Server 版本上进行了测试,包括 2016 SP1 CU1。
复制品
麻烦在于table1
, col7
。
表和查询是原件的部分(和简化)版本。我知道可以用不同的方式重写查询,并且出于某种原因可以避免这个问题,但我们需要避免触及代码,为什么table1
不能搜索的问题仍然存在。
正如 Paul White 所展示的(谢谢!),如果被强制搜索是可用的,所以问题是:为什么搜索没有被优化器选择,以及我们是否可以做一些不同的事情来使搜索按预期发生,而不改变代码?
为了澄清有问题的部分,这里是错误执行计划中的相关扫描:
为什么优化器不选择搜索
TL:DR扩展的计算列定义干扰了优化器最初对连接重新排序的能力。基于不同的起点,基于成本的优化通过优化器采用不同的路径,并以不同的最终计划选择结束。
细节
对于除了最简单的查询之外的所有查询,优化器不会尝试探索任何可能计划的整个空间。相反,它会选择一个看起来合理的起点,然后在一个或多个搜索阶段花费预算内的精力探索逻辑和物理变化,直到找到一个合理的计划。
对于这两种情况,您获得不同计划(具有不同的最终成本估算)的主要原因是起点不同。从不同的地方开始,优化在不同的地方结束(在有限的探索和实施迭代之后)。我希望这是相当直观的。
我提到的起点在某种程度上是基于查询的文本表示,但是当内部树表示经过查询编译的解析、绑定、规范化和简化阶段时,会对内部树表示进行更改。
重要的是,确切的起点在很大程度上取决于优化器选择的初始连接顺序。此选择是在加载统计信息之前以及在得出任何基数估计之前做出的。然而,每个表中的总基数(行数)是已知的,已从系统元数据中获得。
因此,初始连接排序是基于启发式的。例如,优化器尝试重写树,使较小的表在较大的表之前连接,内部连接在外部连接(和交叉连接)之前。
计算列的存在会干扰此过程,尤其是会干扰优化器将外部连接向下推到查询树中的能力。这是因为计算列在连接重新排序发生之前扩展到它的基础表达式中,并且移动连接通过复杂表达式比移动它通过简单列引用要困难得多。
涉及的树相当大,但为了说明,非计算列初始查询树以:(注意顶部的两个外部连接)开始
计算列查询的相同片段是:(注意外连接低得多,扩展的计算列定义,以及(内部)连接排序中的一些其他细微差别)
在设置初始连接顺序后,加载统计信息并在树上执行初始基数估计。以不同的顺序进行连接也会影响这些估计,因此在以后的基于成本的优化过程中会产生连锁反应。
最后,对于本节,将外部连接卡在树的中间可以防止在基于成本的优化期间进行一些进一步的连接重新排序规则匹配。
使用计划指南(或等效的
USE PLAN
提示 -您的查询示例)将搜索策略更改为更面向目标的方法,由提供的模板的一般形状和特征指导。这解释了为什么在使用计划指南或提示时,优化器可以table1
针对计算列架构和非计算列架构找到相同的查找计划。我们是否可以做一些不同的事情来让寻求发生
只有在优化器无法自行找到具有可接受的性能特征的计划时,您才需要担心这一点。
所有常规的调整工具都可能适用。例如,您可以将查询分解为更简单的部分、审查和改进可用的索引、更新或创建新的统计信息……等等。
所有这些都会影响基数估计、通过优化器采用的代码路径,并以微妙的方式影响基于成本的决策。
您可能最终求助于使用提示(或计划指南),但这通常不是理想的解决方案。
评论中的其他问题
不,没有用于执行详尽搜索的跟踪标志,而且您不需要。可能的搜索空间很大,超过宇宙年龄的编译时间不会被接受。此外,优化器并不知道每一种可能的逻辑转换(没有人知道)。
计算列被扩展(就像视图一样)以提供额外的优化机会。扩展可能会在过程的后面匹配回例如持久化列或索引,但这是在初始连接顺序固定之后发生的。