14.3. 使用显式 JOIN 子句控制规划器

通过使用显式JOIN语法,可以在某种程度上控制查询计划器。为了弄清为什么如此重要,我们首先需要一些背景。

在一个简单的联接查询中,例如:

SELECT * FROM a, b, c WHERE a.id = b.id AND b.ref = c.id;

计划者可以随意以任何 Sequences 加入给定的表。例如,它可以生成查询计划,该查询计划使用WHERE条件a.id = b.id将 A 联接到 B,然后使用另一个WHERE条件将 C 联接到该联接表。也可以将 B 加入 C,然后再将 A 加入该结果。或者它可以将 A 连接到 C,然后将它们与 B 连接-但这将是无效的,因为必须形成 A 和 C 的笛卡尔积,因此WHERE子句中没有适用的条件来允许优化连接。 (PostgreSQL 执行程序中的所有联接都发生在两个 Importing 表之间,因此有必要以一种或多种这种方式构建结果.)重要的一点是,这些不同的联接可能性在语义上等效,但执行成本可能大不相同。 。因此,计划者将探索所有这些因素,以找到最有效的查询计划。

当查询仅涉及两个或三个表时,不必担心很多连接 Sequences。但是,随着表数量的扩展,可能的连接 Sequences 数量呈指数增长。超过十个左右的 Importing 表后,对所有可能性进行详尽的搜索不再可行,甚至对于六个或七个表来说,规划也可能需要很长时间。当 Importing 表太多时,PostgreSQL 计划器将通过有限的可能性从穷举搜索切换到遗传概率搜索。 (切换阈值由geqo_threshold运行时参数设置。)遗传搜索花费的时间更少,但不一定能找到最佳方案。

当查询涉及外部联接时,计划者的自由度小于纯(内部)联接的自由度。例如,考虑:

SELECT * FROM a LEFT JOIN (b JOIN c ON (b.ref = c.id)) ON (a.id = b.id);

尽管此查询的限制在表面上与前面的示例相似,但语义有所不同,因为必须为 B 和 C 的联接中没有匹配行的 A 的每一行发出一行。因此,计划者在这里没有选择联接 Sequences 的条件:必须将 B 加入 C,然后再将 A 加入该结果。因此,与上一个查询相比,此查询花费的时间更少。在其他情况下,计划者可能能够确定一个以上的加入订单是安全的。例如,给定:

SELECT * FROM a LEFT JOIN b ON (a.bid = b.id) LEFT JOIN c ON (a.cid = c.id);

首先将 A 加入 B 或 C 是有效的。当前,只有FULL JOIN完全限制了加入 Sequences。涉及LEFT JOINRIGHT JOIN的大多数实际情况可以在某种程度上重新安排。

显式内部联接语法(INNER JOINCROSS JOIN或未修饰的JOIN)在语义上与在FROM中列出 Importing 关系相同,因此它不限制联接 Sequences。

即使大多数类型的JOIN并不完全约束连接 Sequences,也可以指示 PostgreSQL 查询计划程序将所有JOIN子句视为约束连接 Sequences。例如,这三个查询在逻辑上是等效的:

SELECT * FROM a, b, c WHERE a.id = b.id AND b.ref = c.id;
SELECT * FROM a CROSS JOIN b CROSS JOIN c WHERE a.id = b.id AND b.ref = c.id;
SELECT * FROM a JOIN (b JOIN c ON (b.ref = c.id)) ON (a.id = b.id);

但是,如果我们告诉计划者遵守JOIN订单,则第二和第三者比第一者花费的时间更少。对于只有三个表,这种效果不值得担心,但是对于许多表来说,它可能是一个救命稻草。

要强制计划者遵循由显式JOIN s 列出的联接 Sequences,请将join_collapse_limit运行时参数设置为 1.(其他可能的值将在下面讨论.)

您无需完全限制连接 Sequences 即可缩短搜索时间,因为可以在纯FROM列表的项目中使用JOIN运算符。例如,考虑:

SELECT * FROM a CROSS JOIN b, c, d, e WHERE ...;

join_collapse_limit = 1 的情况下,这将迫使计划者先将 A 和 B 联接在一起,然后再将它们联接到其他表,但不会限制其他选择。在此示例中,可能的加入订单数减少了 5 倍。

以这种方式限制计划者的搜索是一种有用的技术,它既可以减少计划时间,又可以将计划者引导至良好的查询计划。如果计划者默认情况下选择了错误的联接 Sequences,则可以通过JOIN语法强制其选择更好的 Sequences-假设您知道更好的 Sequences。建议进行实验。

影响计划时间的一个密切相关的问题是将子查询折叠到其父查询中。例如,考虑:

SELECT *
FROM x, y,
    (SELECT * FROM a, b, c WHERE something) AS ss
WHERE somethingelse;

这种情况可能是由于使用包含联接的视图引起的。该视图的SELECT规则将插入到视图引用的位置,从而产生与上面类似的查询。通常,计划者将尝试将子查询折叠到父查询中,从而得到:

SELECT * FROM x, y, a, b, c WHERE something AND somethingelse;

与单独计划子查询相比,这通常会导致更好的计划。 (例如,外部WHERE条件可能使得首先将 X 连接到 A 消除了 A 的许多行,从而避免了形成子查询的完整逻辑输出的需要.)但是,与此同时,我们增加了计划时间;在这里,我们有一个五向联接问题,代替了两个单独的三向联接问题。由于可能性数量呈指数增长,因此差异很大。如果超过from_collapse_limit FROM项会导致父查询出现问题,计划者将通过不折叠子查询来避免陷入巨大的联接搜索问题。您可以通过向上或向下调整运行时间参数来权衡计划时间与计划质量。

from_collapse_limitjoin_collapse_limit之所以具有相似的名称,是因为它们执行几乎相同的操作:一个控件控制计划程序何时“拉平”子查询,另一个控件控制何时何时拉平显式联接。通常,您可以将join_collapse_limit设置为等于from_collapse_limit(以便显式联接和子查询的行为类似),也可以将join_collapse_limit设置为 1(如果要使用显式联接控制联接 Sequences)。但是,如果要微调计划时间和运行时间之间的权衡,则可以将它们设置为不同的值。