我一直在尝试在 Postgres 12 上优化这个简单的查询,它将多个表连接到一个基本关系。它们每个都有 1 对 1 的关系,并且有 1 万到 1000 万行。
SELECT *
FROM base
LEFT JOIN t1 ON t1.id = base.t1_id
LEFT JOIN t2 ON t2.id = base.t2_id
LEFT JOIN t3 ON t3.id = base.t3_id
LEFT JOIN t4 ON t4.id = base.t4_id
LEFT JOIN t5 ON t5.id = base.t5_id
LEFT JOIN t6 ON t6.id = base.t6_id
LEFT JOIN t7 ON t7.id = base.t7_id
LEFT JOIN t8 ON t8.id = base.t8_id
LEFT JOIN t9 ON t9.id = base.t9_id
(实际关系比这复杂一点,但为了演示目的这样就可以了)
我注意到,当我只执行
SELECT base.id
时,查询仍然非常慢,这看起来很奇怪,因为查询规划器应该知道连接是不必要的,并且不应该影响性能。
然后我注意到8似乎是某种神奇的数字。如果我删除任何一个连接,查询时间就会从 500 毫秒缩短到 1 毫秒。 通过 EXPLAIN,我可以看到 Postgres 在连接 8 个表时仅执行索引扫描,但对于 9 个表,它开始执行顺序扫描。
即使我只做
SELECT base.id
,表的数量也会以某种方式阻碍查询规划器。
我们终于发现postgres中确实有一个名为
join_collapse_limit
的配置设置,默认设置为8。
https://www.postgresql.org/docs/current/runtime-config-query.html#GUC-JOIN-COLLAPSE-LIMIT
只要产生不超过这么多项目的列表,规划器就会将显式 JOIN 构造(FULL JOIN 除外)重写为 FROM 项目列表。较小的值会减少规划时间,但可能会产生较差的查询计划。默认情况下,此变量的设置与 from_collapse_limit 相同,适用于大多数用途。将其设置为 1 可防止显式 JOIN 的任何重新排序。因此,查询中指定的显式连接顺序将是关系连接的实际顺序。由于查询规划器并不总是选择最佳连接顺序,因此高级用户可以选择暂时将此变量设置为 1,然后明确指定他们想要的连接顺序。
阅读本文后,我们决定增加限制以及其他值,例如
from_collapse_limit
和geco_threshold
。请注意,查询计划时间随着联接数量呈指数增长,因此限制的存在是有原因的,不应粗心地增加。