2025年9月25日: PostgreSQL 18 发布!
支持的版本: 当前 (18) / 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

51.5. 查询规划器/优化器 #

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

注意

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

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

51.5.1. 生成可能的计划 #

规划器/优化器首先为查询中使用的每个单独关系(表)生成扫描计划。可能的计划由每个关系上可用的索引决定。关系上总是存在顺序扫描的可能性,因此总是会创建一个顺序扫描计划。假设一个关系上定义了索引(例如 B-tree 索引),并且查询包含限制 relation.attribute OPR constant。如果 relation.attribute 恰好匹配 B-tree 索引的键,并且 OPR 是索引的 操作符类 中列出的运算符之一,则会创建一个使用 B-tree 索引扫描关系的另一个计划。如果存在其他索引,并且查询中的限制恰好匹配索引的键,则会考虑进一步的计划。对于具有可以匹配查询 ORDER BY 子句(如果有)的排序顺序的索引,或者可能对合并连接(如下文所述)有用的排序顺序的索引,也会生成索引扫描计划。

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

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

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

  • 哈希连接:首先扫描右关系并将其加载到哈希表中,使用其连接属性作为哈希键。然后扫描左关系,并使用找到的每一行的相应值作为哈希键来定位哈希表中的匹配行。

当查询涉及三个或更多关系时,最终结果必须通过一系列连接步骤构建,每个步骤有两个输入。规划器会检查不同的连接顺序以找到成本最低的。

如果查询涉及的关系少于 geqo_threshold 个,将进行近乎穷举的搜索来查找最佳连接顺序。规划器优先考虑任何两个关系之间的连接,这些关系在 WHERE 条件中存在相应的连接子句(即,存在类似 where rel1.attr1=rel2.attr2 的限制)。只有在没有其他选择时才考虑没有连接子句的连接对,也就是说,某个关系与其他关系没有任何可用的连接子句。对于规划器考虑的每个连接对,都会生成所有可能的计划,并选择(估计)成本最低的那个。

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

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

提交更正

如果您在文档中看到任何不正确、与您对特定功能的体验不符或需要进一步阐明的内容,请使用 此表单 报告文档问题。