JOIN
子句控制查询规划器 #可以通过使用显式 JOIN
语法在一定程度上控制查询规划器。要了解为什么这很重要,我们首先需要一些背景知识。
在一个简单的连接查询中,例如
SELECT * FROM a, b, c WHERE a.id = b.id AND b.ref = c.id;
规划器可以自由地以任何顺序连接给定的表。例如,它可以生成一个查询计划,该计划使用 WHERE
条件 a.id = b.id
将 A 连接到 B,然后使用另一个 WHERE
条件将 C 连接到这个连接的表。或者它可以将 B 连接到 C,然后将 A 连接到该结果。或者它可以将 A 连接到 C,然后将它们与 B 连接 - 但这将是低效的,因为必须形成 A 和 C 的完整笛卡尔积,因为在 WHERE
子句中没有适用的条件来允许优化连接。(PostgreSQL 执行器中的所有连接都发生在两个输入表之间,因此有必要以这些方式之一构建结果。)重要的一点是,这些不同的连接可能性给出语义上等效的结果,但可能具有巨大的执行成本差异。因此,规划器将探索所有这些可能性,以尝试找到最有效的查询计划。
当查询只涉及两到三个表时,没有多少连接顺序需要担心。但是,随着表数量的增加,可能的连接顺序的数量呈指数级增长。超过十个左右的输入表时,对所有可能性进行详尽搜索不再实用,即使对于六个或七个表,规划也可能需要很长的恼人时间。当输入表太多时,PostgreSQL 规划器将从详尽搜索切换到对有限数量的可能性进行遗传概率搜索。(切换阈值由 geqo_threshold 运行时参数设置。)遗传搜索花费的时间较少,但它不一定会找到最佳的计划。
当查询涉及外连接时,规划器的自由度比普通(内部)连接要小。例如,考虑
SELECT * FROM a LEFT JOIN (b JOIN c ON (b.ref = c.id)) ON (a.id = b.id);
尽管此查询的限制在表面上与前面的示例相似,但语义是不同的,因为必须为 A 的每一行发出一个行,该行在 B 和 C 的连接中没有匹配的行。因此,规划器在此处没有选择连接顺序:它必须将 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
完全约束连接顺序。大多数涉及 LEFT JOIN
或 RIGHT JOIN
的实际案例都可以在一定程度上重新排列。
显式内部连接语法(INNER JOIN
、CROSS JOIN
或未修饰的 JOIN
)在语义上与在 FROM
中列出输入关系相同,因此它不约束连接顺序。
即使大多数类型的 JOIN
不完全约束连接顺序,也可以指示 PostgreSQL 查询规划器将所有 JOIN
子句视为无论如何都约束连接顺序。例如,这三个查询在逻辑上是等效的
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
设置的连接顺序,请将 join_collapse_limit 运行时参数设置为 1。(下面讨论其他可能的值。)
您不需要完全约束连接顺序来减少搜索时间,因为在普通 FROM
列表的项中使用 JOIN
运算符是可以的。例如,考虑
SELECT * FROM a CROSS JOIN b, c, d, e WHERE ...;
当 join_collapse_limit
= 1 时,这强制规划器将 A 连接到 B,然后再将它们连接到其他表,但不以其他方式约束其选择。在本例中,可能的连接顺序数减少了 5 倍。
以这种方式约束规划器的搜索是一种有用的技术,既可以减少规划时间,又可以将规划器定向到良好的查询计划。如果规划器默认选择错误的连接顺序,则可以通过 JOIN
语法强制其选择更好的顺序 - 假设您知道更好的顺序。建议进行实验。
一个密切相关的影响规划时间的问题是将子查询折叠到其父查询中。例如,考虑
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_limit 和 join_collapse_limit 命名相似,因为它们几乎执行相同的操作:一个控制规划器何时将“展开”子查询,另一个控制它何时展开显式连接。通常,您可以将 join_collapse_limit
设置为等于 from_collapse_limit
(以便显式连接和子查询的行为类似),或者将 join_collapse_limit
设置为 1(如果您想使用显式连接来控制连接顺序)。但是,如果您尝试微调计划时间和运行时间之间的权衡,则可以对其进行不同的设置。
如果您发现文档中有任何不正确、与特定功能的体验不符或需要进一步澄清的内容,请使用此表单报告文档问题。