对于我正在尝试优化的中等复杂查询,我注意到删除TOP n
子句会更改执行计划。我会猜到,当查询包含TOP n
数据库引擎时会忽略该TOP
子句运行查询,然后最后将该结果集缩小到请求的n行数。图形执行计划似乎表明情况就是这样——TOP
是“最后”一步。但似乎还有更多的事情发生。
我的问题是,TOP n 子句如何(以及为什么)影响查询的执行计划?
这是我的情况的简化版本:
该查询匹配两个表 A 和 B 中的行。
如果没有该TOP
子句,优化器估计表 A 中将有 19k 行,表 B 中将有 46k 行。返回的实际行数是 A 的 16k 和 B 的 13k。哈希匹配用于连接这两个结果集以获得总共 69 行(然后应用排序)。这个查询发生得很快。
当我添加TOP 1001
优化器时不使用哈希匹配;相反,它首先对表 A 中的结果进行排序(相同的估计/实际为 19k/16k)并针对表 B 执行嵌套循环。表 B 的估计行数现在为 1,奇怪的是TOP n
直接影响针对 B 的估计执行次数(索引搜索)——它似乎总是2n+1,或者在我的情况下是 2003 。如果我改变,这个估计会相应地改变TOP n
。当然,由于这是一个嵌套连接,实际执行次数为 16k(表 A 中的行数),这会减慢查询速度。
查询有一个ORDER BY
子句。在计划中发生这种排序的位置添加TOP
更改,但我更关心它如何影响对表 B 执行索引搜索的次数。
实际情况要复杂一些,但这抓住了基本的想法/行为。两个表都使用索引搜索进行搜索。这是 SQL Server 2008 R2 企业版。
上述措辞的方式让我认为您可能对查询的执行方式有一个不正确的心理画面。查询计划中的运算符不是一个步骤(前一步的完整结果集由下一个步骤评估。
SQL Server 使用流水线执行模型,其中每个运算符都公开Init()、GetRow()和Close()等方法。正如GetRow()名称所暗示的那样,操作员根据需要一次生成一行(根据其父操作员的要求)。这在 Books Online Logical and Physical Operators 参考中有记录,在我的博客文章Why Query Plans Run Backwards中有更多详细信息。这种一次一行的模型对于为查询执行形成合理的直觉至关重要。
一些逻辑操作
TOP
,如半连接和FAST n
查询提示,会影响查询优化器对执行计划备选方案进行成本计算的方式。基本思想是,一个可能的计划形状可能比经过优化以返回所有行的不同计划更快地返回前n行。例如,索引嵌套循环连接通常是返回少量行的最快方法,尽管散列或合并连接与扫描可能在较大的集合上更有效。查询优化器对这些选择进行推理的方式是在操作逻辑树中的特定点设置行目标。
行目标修改查询计划备选方案的成本计算方式。它的本质是优化器首先计算每个运算符的成本,就好像需要完整的结果集一样,在适当的点设置一个行目标,然后沿着计划树向下工作,估计它预计需要检查的行数以满足行目标。
例如,逻辑
TOP(10)
在逻辑查询树中的特定点设置行目标 10。导致行目标的操作员成本被修改以估计他们需要生产多少行才能满足行目标。此计算可能会变得复杂,因此通过完整的示例和带注释的执行计划更容易理解所有这些。行目标可能影响的不仅仅是连接类型的选择或查找和查找是否比扫描更受欢迎。更多细节在这里。与往常一样,基于行目标选择的执行计划取决于优化器的推理能力和提供给它的信息质量。并非每个具有行目标的计划在实践中都会更快地生成所需的行数,但根据成本计算模型,它会。
在行目标计划被证明不是更快的情况下,通常有一些方法可以修改查询或向优化器提供更好的信息,以便自然选择的计划是最好的。哪个选项适合您的情况当然取决于细节。行目标功能通常非常有效(尽管在并行执行计划中使用时需要注意一个错误)。
您的特定查询和计划可能不适合在此处进行详细分析(如果您愿意,请务必提供实际的执行计划),但希望此处概述的想法能让您取得进展。
当您使用 TOP 时,优化器看到了减少工作量的机会。如果您要求 10 行,那么很有可能它不需要消耗整个集合。因此 TOP 运算符可以被推到更远的右侧。它将继续向下一个运算符(在其右侧)请求行,直到收到足够的数据。
您指出,如果没有 TOP,查询会在最后对数据进行排序。如果引擎可以提前知道连接将满足多少行,它可能会选择使用类似的计划,将 TOP 定位在左侧。但是由于进行哈希匹配的工作量相对较高,并且可能没有合并连接的选项,优化器可能更喜欢进一步向右过滤 TOP。
当查询表 B 时,它一次获取一行。这就是估计值为 1 的原因。它还假设它只会在 50% 的时间内找到该行。所以它猜测它需要 2n+1 次才能找到它。