14.3. 使用显式条款控制计划程序JOIN
可以使用显式语法在一定程度上控制查询计划器。要了解为什么这很重要,我们首先需要一些背景知识。JOIN
在简单的联接查询中,例如:
SELECT * FROM a, b, c WHERE a.id = b.id AND b.ref = c.id;
计划员可以自由地以任何顺序连接给定的表。例如,它可以生成一个查询计划,该计划使用条件将 A 连接到 B,然后使用另一个条件将 C 连接到此联接表。或者它可以将 B 连接到 C,然后将 A 连接到该结果。或者它可以将 A 连接到 C,然后将它们与 B 连接起来——但这将是低效的,因为必须形成 A 和 C 的完整笛卡尔乘积,并且子句中没有允许优化连接的适用条件。(PostgreSQL 执行器中的所有连接都发生在两个输入表之间,因此有必要以其中一种或另一种方式构建结果。重要的一点是,这些不同的联接可能性给出了语义上等效的结果,但可能具有截然不同的执行成本。因此,计划人员将探索所有这些,以尝试找到最有效的查询计划。WHERE``a.id = b.id``WHERE``WHERE
当查询仅涉及两个或三个表时,无需担心太多联接顺序。但是,随着表数量的增加,可能的联接顺序的数量呈指数级增长。超过十个左右的输入表,对所有可能性进行详尽的搜索不再切实际,即使对于六七个表,规划也可能需要很长时间。当输入表太多时,PostgreSQL 规划器将通过有限数量的可能性从穷举搜索切换到遗传概率搜索。(切换阈值由geqo_threshold运行时参数设置。基因搜索花费的时间更少,但它不一定能找到最好的计划。
当查询涉及外部联接时,规划器的自由度低于普通(内部)联接的自由度。例如,考虑:
SELECT * FROM a LEFT JOIN (b JOIN c ON (b.ref = c.id)) ON (a.id = b.id);
尽管此查询的限制表面上与前面的示例相似,但语义是不同的,因为必须为在 B 和 C 的连接中没有匹配行的 A 的每一行发出一行。因此,计划程序在这里没有连接顺序的选择:它必须将 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
显式内部连接语法 (, 或 unadorned) 在语义上与在 中列出输入关系相同,因此它不约束连接顺序。INNER JOIN``CROSS JOIN``JOIN``FROM
尽管大多数类型的 T都没有完全约束连接顺序,但无论如何都可以指示 PostgreSQL 查询规划器将所有子句视为约束连接顺序。例如,这三个查询在逻辑上是等效的:JOIN``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
若要强制计划程序遵循显式 s 布置的连接顺序,请将 join_collapse_limit 运行时参数设置为 1。(下面将讨论其他可能的值。JOIN
您不需要为了缩短搜索时间而完全限制联接顺序,因为可以在普通列表的项目中使用运算符。例如,考虑:JOIN``FROM
SELECT * FROM a CROSS JOIN b, c, d, e WHERE ...;
使用 = 1 时,这将强制计划程序在将它们连接到其他表之前将 A 连接到 B,但不会限制其选择。在此示例中,可能的联接顺序数减少了 5 倍。join_collapse_limit
以这种方式约束计划员的搜索是一种有用的技术,既可以减少计划时间,也可以将计划员引导到一个好的查询计划。如果计划程序默认选择了错误的联接顺序,您可以通过语法强制它选择更好的顺序 — 假设您知道更好的顺序。建议进行实验。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;
这通常比单独规划子查询会产生更好的计划。(例如,外部条件可能是将 X 连接到 A 首先消除 A 的许多行,从而避免了形成子查询的完整逻辑输出的需要。但与此同时,我们增加了规划时间;在这里,我们有一个五向连接问题,替换了两个单独的三向连接问题。由于可能性数量的指数增长,这会产生很大的不同。规划器试图避免陷入巨大的联接搜索问题,如果超过项目会导致父查询,则不折叠子查询。您可以通过向上或向下调整此运行时参数来权衡计划时间与计划质量。WHERE``from_collapse_limit``FROM
from_collapse_limit和join_collapse_limit的名称相似,因为它们执行几乎相同的操作:一个控制计划器何时“展平”子查询,另一个控制计划器何时展平显式连接。通常,您可以设置为等于(以便显式联接和子查询的作用类似)或设置为 1(如果要使用显式联接控制联接顺序)。但是,如果您尝试微调计划时间和运行时之间的权衡,则可以以不同的方式设置它们。join_collapse_limit``from_collapse_limit``join_collapse_limit