计划器/优化器的任务是创建最优的执行计划。给定的 SQL 查询(以及查询树)实际上可以通过多种不同的方式执行,每种方式都会产生相同的结果集。如果在计算上可行,查询优化器将检查所有这些可能的执行计划,最终选择预计运行速度最快的执行计划。
在某些情况下,检查查询可以执行的每种可能方式将花费过多的时间和内存。特别是,当执行涉及大量连接操作的查询时,就会发生这种情况。为了在合理的时间内确定合理的(不一定是最优的)查询计划,PostgreSQL 在连接操作数超过阈值时(参见 geqo_threshold)使用遗传查询优化器(参见 第 60 章)。
计划器的搜索过程实际上使用称为路径的数据结构,它只是计划的简化表示,其中仅包含计划器做出决策所需的信息。确定最便宜的路径后,将构建一个完整的计划树以传递给执行器。这以执行器运行它所需的足够详细的信息表示所需的执行计划。在本节的其余部分,我们将忽略路径和计划之间的区别。
计划器/优化器首先为查询中使用的每个单独的关系(表)生成扫描计划。可能的计划由每个关系上的可用索引确定。始终有可能对关系执行顺序扫描,因此始终创建顺序扫描计划。假设在关系上定义了一个索引(例如 B 树索引),并且查询包含限制relation.attribute OPR constant
。如果relation.attribute
恰好与 B 树索引的键匹配,并且OPR
是索引的操作符类中列出的操作符之一,则将使用 B 树索引扫描关系来创建另一个计划。如果存在其他索引并且查询中的限制恰好与索引的键匹配,则将考虑其他计划。索引扫描计划也为具有可以匹配查询的ORDER BY
子句(如果有)的排序顺序或可能对合并连接有用的排序顺序的索引生成。(参见下文)。
如果查询需要连接两个或多个关系,则在找到所有可行的单关系扫描计划后,将考虑连接关系的计划。三种可用的连接策略是
嵌套循环连接:对于在左关系中找到的每一行,都会扫描一次右关系。这种策略易于实现,但可能非常耗时。(但是,如果可以使用索引扫描扫描右关系,这可能是一个好策略。可以将左关系当前行的值用作右关系的索引扫描的键。)
合并连接:在连接开始之前,每个关系都根据连接属性排序。然后,这两个关系并行扫描,并将匹配的行组合起来形成连接行。这种连接很有吸引力,因为每个关系只需要扫描一次。所需的排序可以通过显式排序步骤实现,也可以通过使用连接键上的索引以正确的顺序扫描关系来实现。
哈希连接:首先扫描右关系并将其加载到哈希表中,使用其连接属性作为哈希键。接下来扫描左关系,并将找到的每一行的相应值用作哈希键以在表中查找匹配的行。
当查询涉及两个以上的关系时,必须通过连接步骤的树来构建最终结果,每个步骤有两个输入。计划器检查不同的可能连接序列以找到最便宜的一个。
如果查询使用的关系少于 geqo_threshold,则将进行接近穷举的搜索以找到最佳连接序列。计划器优先考虑任何两个关系之间的连接,这两个关系在WHERE
限定条件中存在相应的连接子句(即,存在类似于where rel1.attr1=rel2.attr2
的限制)。只有在没有其他选择时才会考虑没有连接子句的连接对,也就是说,特定关系没有可用于任何其他关系的连接子句。为计划器考虑的每个连接对生成所有可能的计划,并选择(估计为)最便宜的那个。
当超过 geqo_threshold
时,考虑的连接序列由启发式算法确定,如 第 60 章 中所述。否则,过程相同。
完成的计划树由基本关系的顺序或索引扫描组成,以及根据需要加上嵌套循环、合并或哈希连接节点,以及任何需要的辅助步骤,例如排序节点或聚合函数计算节点。大多数这些计划节点类型还具有执行选择(丢弃不满足指定布尔条件的行)和投影(基于给定列值计算派生列集,即根据需要评估标量表达式)的能力。计划器的一项职责是将WHERE
子句中的选择条件和所需输出表达式的计算附加到计划树的最合适的节点。
如果您在文档中看到任何不正确的内容,与您对特定功能的体验不符,或者需要进一步澄清,请使用 此表单 报告文档问题。