支持的版本:当前 (17) / 16 / 15 / 14 / 13
开发版本:devel
不支持的版本:12 / 11 / 10 / 9.6 / 9.5 / 9.4 / 9.3 / 9.2 / 9.1 / 9.0 / 8.4 / 8.3 / 8.2 / 8.1 / 8.0 / 7.4 / 7.3 / 7.2 / 7.1

50.5. 规划器/优化器 #

规划器/优化器的任务是创建最佳执行计划。一个给定的 SQL 查询(以及相应的查询树)实际上可以通过多种不同的方式执行,每种方式都会产生相同的结果集。如果计算上可行,查询优化器将检查每个可能的执行计划,最终选择预计运行速度最快的执行计划。

注意

在某些情况下,检查查询可以执行的每种可能方式会花费过多的时间和内存。特别是,当执行涉及大量连接操作的查询时会发生这种情况。为了在合理的时间内确定一个合理(不一定是最优)的查询计划,当连接数超过阈值时,PostgreSQL 使用遗传查询优化器(请参阅第 60 章)(请参阅geqo_threshold)。

规划器的搜索过程实际上处理称为路径的数据结构,这些结构只是计划的简化表示,仅包含规划器做出决策所需的信息。在确定最便宜的路径后,将构建一个完整的计划树以传递给执行器。这以足够的细节表示所需的执行计划,以便执行器运行它。在本节的其余部分,我们将忽略路径和计划之间的区别。

50.5.1. 生成可能的计划 #

规划器/优化器首先为查询中使用的每个单独的关系(表)生成扫描计划。可能的计划由每个关系上可用的索引确定。总是可以对关系执行顺序扫描,因此始终会创建一个顺序扫描计划。假设在一个关系上定义了一个索引(例如 B 树索引),并且查询包含限制relation.attribute OPR constant。如果relation.attribute恰好与 B 树索引的键匹配,并且OPR是索引的运算符类中列出的运算符之一,则将创建另一个计划,使用 B 树索引来扫描该关系。如果存在其他索引,并且查询中的限制恰好与索引的键匹配,则将考虑其他计划。还会为具有可以匹配查询的ORDER BY子句(如果有)的排序顺序或可能对合并连接有用的排序顺序的索引生成索引扫描计划(请参阅下文)。

如果查询需要连接两个或多个关系,则在找到扫描单个关系的所有可行计划后,将考虑连接关系的计划。三种可用的连接策略是

  • 嵌套循环连接:对于在左关系中找到的每一行,右关系都会扫描一次。此策略易于实现,但可能非常耗时。(但是,如果可以使用索引扫描来扫描右关系,则这可能是一个很好的策略。可以使用左关系的当前行中的值作为右关系的索引扫描的键。)

  • 合并连接:在连接开始之前,每个关系都按连接属性排序。然后并行扫描两个关系,并将匹配的行组合以形成连接行。这种连接很有吸引力,因为每个关系只需要扫描一次。所需的排序可以通过显式排序步骤来实现,也可以通过使用连接键上的索引按正确的顺序扫描关系来实现。

  • 哈希连接:首先扫描右关系并将其加载到哈希表中,使用其连接属性作为哈希键。接下来,扫描左关系,并将找到的每一行的适当值用作哈希键,以在表中查找匹配的行。

当查询涉及两个以上的关系时,必须通过一系列连接步骤(每个步骤都有两个输入)来构建最终结果。规划器会检查不同的可能连接顺序,以找到最便宜的一个。

如果查询使用的关系少于geqo_threshold个,则会进行近乎详尽的搜索以找到最佳连接顺序。规划器优先考虑在WHERE限定条件中存在相应连接子句的任意两个关系之间的连接(即,存在诸如where rel1.attr1=rel2.attr2之类的限制)。仅当别无选择时才考虑没有连接子句的连接对,也就是说,特定关系没有任何可用于任何其他关系的可用连接子句。为规划器考虑的每个连接对生成所有可能的计划,并选择(估计)最便宜的计划。

当超过geqo_threshold时,所考虑的连接顺序由启发式方法确定,如第 60 章中所述。否则,过程是相同的。

完成的计划树由基本关系的顺序或索引扫描,以及根据需要添加的嵌套循环、合并或哈希连接节点,以及任何需要的辅助步骤(如排序节点或聚合函数计算节点)组成。这些计划节点类型中的大多数还具有执行选择(丢弃不满足指定的布尔条件的行)和投影(基于给定的列值计算派生列集,即,在需要时评估标量表达式)的附加能力。规划器的职责之一是将来自WHERE子句的选择条件和所需输出表达式的计算附加到计划树的最合适节点。

提交更正

如果您在文档中发现任何不正确、与您使用特定功能的经验不符或需要进一步澄清的内容,请使用此表格报告文档问题。