当一条 SQL 连接的表越来越多,PostgreSQL 需要评估的连接顺序会快速膨胀。遗传查询优化器 GEQO 用近似搜索控制规划时间,为此提供了七个 GUC 参数。但参数多不等于都值得调整:日常性能治理中,最有实际意义的通常是 geqo_threshold,其余参数更适合作为 PostgreSQL 内部算法的控制面,而不是常规调优旋钮。
GEQO 解决的是规划复杂度,不是执行速度本身
对于少量表连接,PostgreSQL 可以较充分地比较不同连接顺序。表数增加后,候选顺序的数量呈组合式增长,规划器不可能无限搜索。
GEQO 的作用,是在连接关系达到一定规模后,使用遗传算法寻找一个足够好的连接计划。它交换的是确定性和搜索完整度,以避免规划时间失控。因此,看到 GEQO 参与规划,并不表示数据库执行引擎变了,也不保证查询运行得更快;它主要改变的是 PostgreSQL 如何寻找连接顺序。
GEQO 家族包含七个设置:
| 参数 | 作用 | 日常建议 |
|---|---|---|
geqo |
总开关 | 通常保持开启 |
geqo_threshold |
达到多少个 FROM 项后考虑使用 GEQO | 最值得按工作负载测试 |
geqo_effort |
综合控制搜索工作量 | 保持默认值 |
geqo_pool_size |
候选计划种群规模 | 保持自动计算 |
geqo_generations |
算法迭代代数 | 保持自动计算 |
geqo_selection_bias |
控制选择压力 | 除算法实验外不要调整 |
geqo_seed |
随机种子 | 可用于复现实验,不是性能调优项 |
具体默认值可能随 PostgreSQL 版本和发行配置而不同,应以当前实例的 pg_settings 为准。
为什么通常只调整 geqo_threshold
geqo_threshold 直接表达了一个可以通过基准测试回答的工程问题:从多少个连接项开始,穷举式规划的成本已经不划算?
阈值太低,普通查询会过早进入近似搜索,可能错过更好的连接顺序;阈值太高,复杂查询则可能把大量时间耗在规划阶段。这个取舍可以用真实 SQL、规划耗时和端到端延迟衡量。
相比之下,geqo_pool_size、geqo_generations 和 geqo_selection_bias 暴露的是遗传算法内部细节。单独改变其中一个值,往往会同时影响搜索成本、随机性和结果质量,很难从业务指标反推出稳定结论。geqo_effort 虽然提供了更高层的工作量控制,但默认参数之间已经存在配合关系,没有基准数据时继续细调通常只会扩大试验空间。
geqo_seed 是个例外:它对排查计划波动有用。固定种子可以帮助复现实验结果,但不应把某个种子当成长期性能优化,因为数据分布、统计信息和 SQL 形态都会变化。
在当前实例上查看七个参数
下面的 SQL 可以直接在 psql 或其他 PostgreSQL 客户端中运行:
SELECT name,
setting,
unit,
context,
source,
pending_restart
FROM pg_settings
WHERE name IN (
'geqo',
'geqo_threshold',
'geqo_effort',
'geqo_pool_size',
'geqo_generations',
'geqo_selection_bias',
'geqo_seed'
)
ORDER BY name;
除了当前值,还要关注 source。如果某个内部参数来自 postgresql.conf、ALTER SYSTEM 或角色级设置,而团队并不知道为什么,它可能是历史调优遗留项。此时应先还原实验背景,不要继续叠加参数。
可以这样实践:只在事务中比较阈值
下面是假设数据库中已经存在一条多表查询时的测试模板。将示例 SQL 替换为生产查询的脱敏版本,并在接近生产数据量的环境执行。
BEGIN;
SET LOCAL geqo_threshold = 12;
EXPLAIN (ANALYZE, BUFFERS, SETTINGS, TIMING OFF)
SELECT o.id, c.name, p.status
FROM orders AS o
JOIN customers AS c ON c.id = o.customer_id
JOIN payments AS p ON p.order_id = o.id
WHERE o.created_at >= CURRENT_DATE - INTERVAL '7 days';
ROLLBACK;
这个示例只有三个连接项,通常不足以触发 GEQO;它展示的是安全的测试方法。对于真实的十几个表连接查询,可以分别测试当前阈值、更高阈值,以及临时关闭 GEQO 的结果:
BEGIN;
SET LOCAL geqo = off;
EXPLAIN (ANALYZE, BUFFERS, SETTINGS, TIMING OFF)
SELECT /* 替换为真实的多表查询 */ 1;
ROLLBACK;
SET LOCAL 只在当前事务内生效,ROLLBACK 后不会污染连接池中的后续会话。不要只比较执行节点的成本估算,还应记录客户端观察到的总耗时,因为 EXPLAIN ANALYZE 输出中的 Planning Time 才能揭示提高阈值的代价。
若要从命令行重复测试,可以这样运行:
for threshold in 10 12 14 16; do
echo "=== geqo_threshold=${threshold} ==="
psql "$DATABASE_URL" -X -v ON_ERROR_STOP=1 <<SQL
BEGIN;
SET LOCAL geqo_threshold = ${threshold};
EXPLAIN (ANALYZE, BUFFERS, SETTINGS, TIMING OFF)
SELECT /* 替换为真实多表查询 */ 1;
ROLLBACK;
SQL
done
运行前需要设置 DATABASE_URL,并替换查询。建议每个阈值执行多次、打乱顺序,同时区分冷缓存和热缓存结果。GEQO 带有随机搜索特征,单次测试不足以支持配置变更。
落地时看这几项
调整之前,先确认问题确实发生在规划阶段。如果主要时间消耗在扫描、排序、网络传输或锁等待,修改 GEQO 参数不会解决根因。
生产变更可以遵循以下检查清单:
- 用
EXPLAIN (ANALYZE, BUFFERS, SETTINGS)同时记录规划时间、执行时间和实际行数。 - 使用真实的数据规模与统计信息,避免只在空库或小样本上测试。
- 优先按会话或事务验证
geqo_threshold,不要直接全局修改。 - 除复现实验外,保留
geqo_seed默认值。 - 保持
geqo_pool_size和geqo_generations的自动计算,除非正在做受控的优化器研究。 - 检查复杂 SQL 是否能通过拆分、预聚合、修复连接条件或更新统计信息降低规划难度。
GEQO 的七个参数看起来像一整套调优面板,实际更合理的操作方式是把它们分成两类:geqo_threshold 决定何时切换搜索策略,值得围绕真实工作负载验证;其余参数主要决定遗传算法如何运行,通常交给 PostgreSQL 默认值更稳妥。