51.5. Planner/Optimizer

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

Note

在某些情况下,检查执行查询的每种可能方式将花费大量时间和内存空间。特别是在执行涉及大量联接操作的查询时,会发生这种情况。为了在合理的时间内确定合理的(不一定是最优的)查询计划,当联接数超过阈值(参见geqo_threshold)时,PostgreSQL 使用* Genetic Query Optimizer *(参见Chapter 60)。

计划者的搜索过程实际上与称为* paths *的数据结构配合使用,这些数据结构只是计划的简化表示,仅包含计划者做出决定所需的信息。确定最便宜的路径后,将构建完整的“计划树”以传递给执行者。这足够详细地表示了所需的执行计划,供执行者运行。在本节的其余部分,我们将忽略路径和计划之间的区别。

51 .5.1. 生成可能的计划

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

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

    • nested loop join *:对在左关系中找到的每一行扫描一次右关系。此策略易于实施,但非常耗时。 (但是,如果可以使用索引扫描来扫描右关系,那么这可能是一个很好的策略.可以将左关系的当前行中的值用作右索引扫描的键.)
    • merge join *:在开始连接之前,将每个关系按连接属性排序。然后,并行扫描这两个关系,并将匹配的行合并以形成连接行。这种连接更具吸引力,因为每个关系只需扫描一次。所需的排序可以通过明确的排序步骤来实现,也可以通过使用连接键上的索引以正确的 Sequences 扫描关系来实现。
    • hash join *:首先将正确的关系扫描并使用其连接属性作为哈希键加载到哈希表中。接下来,扫描左关系,并将找到的每一行的适当值用作哈希键,以在表中找到匹配的行。

当查询涉及两个以上的关系时,最终结果必须由连接步骤树构建,每个步骤都有两个 Importing。计划者检查不同的可能连接 Sequences 以找到最便宜的连接 Sequences。

如果查询使用的关系少于geqo_threshold,则进行一次几乎穷举的搜索以找到最佳连接 Sequences。计划者优先考虑在WHERE资格条件中存在对应的连接子句的任何两个关系之间的联接(即存在类似where rel1.attr1=rel2.attr2的限制)。仅当没有其他选择时,才考虑不具有 join 子句的联接对,即,特定关系对任何其他关系都没有可用的 join 子句。为计划者考虑的每个联接对生成所有可能的计划,然后选择(估计)最便宜的一对。

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

完成的计划树包括对基本关系的 Sequences 或索引扫描,以及根据需要的嵌套循环,合并或哈希联接节点,以及所需的任何辅助步骤,例如排序节点或聚合函数计算节点。这些计划节点类型中的大多数具有执行* selection (丢弃不满足指定布尔条件的行)和 projection *(基于给定列值的派生列集的计算,即标量求值)的附加功能。表达式)。计划者的职责之一是将WHERE子句中的选择条件附加起来,并将所需的输出表达式计算到计划树的最适当节点上。