受支持版本: 当前版本 (18) / 17 / 16 / 15 / 14
开发版本: devel

50.5. 规划器/优化器 #

规划器/优化器的任务是创建一个最优的执行计划。给定的一个 SQL 查询(因此也就是一棵查询树)实际上可以用多种不同方式执行,而这些方式都会产生同样的结果集。如果在计算上可行,查询优化器将考察这些可能执行计划中的每一种,并最终选择预计运行最快的那个执行计划。

Note

在某些情况下,考察查询的每一种可能执行方式会耗费过多时间和内存。尤其是在执行涉及大量连接操作的查询时更是如此。为了在合理时间内确定一个合理的(不一定最优的)查询计划,PostgreSQL 会使用 遗传查询优化器(见 Chapter 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 时,被考虑的连接顺序将由启发式方法决定,如 Chapter 60 中所述。除此之外,处理过程与前面相同。

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

提交更正

如果您发现文档中有不正确的内容、与您使用特定功能的经验不符或需要进一步说明,请使用此表单来报告文档问题。