内容提要
PostgreSQL 19 新增 pg_plan_advice 与 pg_stash_advice 模块,可将执行计划导出为不含代价与估算的建议字符串并强制复用,避免数据变化导致计划变差,且便于跨版本比较;建议按 query id 存储以自动应用。但固定计划会丧失规划器自适应能力,应只保留关键项并谨慎使用。
延伸解读
计划建议的跨版本比较价值
文章指出,升级前最值得做的比较是:用旧版本(如 PostgreSQL 16)的 EXPLAIN 输出与 PostgreSQL 19 的 pg_plan_advice 输出进行对比。由于建议字符串不含代价和估算,两次运行相同计划会产生完全一致的输出,任何差异都是真实差异。通过 sqlfmt 的 -canonical 选项,可以消除版本间编号和模式限定符的差异,使升级是否改变了计划一目了然。
建议不生效时的诊断信号
当建议与规划器能力冲突时,输出会标注 /* matched, failed */。例如关闭 hash join 后仍要求 HASH_JOIN,建议会被理解并匹配到正确位置,但规划器无法执行。这个注解至关重要:它让用户能确认建议是否真正生效,避免长期携带无效配置却误以为计划已被固定。
按 query id 自动应用建议的机制
pg_stash_advice 将建议字符串与 query id 映射存储在共享内存中,并通过 shared_preload_libraries 预加载。设置 pg_stash_advice.stash_name 后,任何匹配该 query id 的查询都会自动应用建议,无需修改应用代码或重写查询。query id 可从 pg_stat_statements 获取,与 pg_plan_advice 计算方式一致,便于定位问题查询。
固定计划的风险与使用纪律
文档反复警告:规划器随数据变化调整计划的能力是特性,而建议会剥夺这一能力。若数据分布变化,固定计划可能在新数据上表现糟糕。因此应只保留关键建议(如 JOIN_ORDER),删除其余部分,让规划器在其他方面保持自由。应用建议还会增加规划时间,建议按查询而非集群范围使用。
Q&A
PostgreSQL 19 中 pg_plan_advice 和 pg_stash_advice 模块分别有什么作用?
pg_plan_advice 可以将执行计划导出为不含代价与估算的建议字符串,并强制规划器后续采用该计划;pg_stash_advice 则将这些建议字符串按 query id 存储在共享内存中,自动应用到匹配的查询上。
为什么计划建议字符串不包含代价和行数估算?
因为建议描述的是计划的结果(如连接顺序、连接方法、扫描方法、并行性),而不是产生该计划的推理过程。不包含代价和估算,使得建议在统计信息变化后依然有效,并且便于比较不同计划。
如何在不升级到 PostgreSQL 19 的情况下获得计划建议字符串?
可以使用 sqlfmt 工具,从任意版本的普通 EXPLAIN 输出中重建出建议字符串。例如运行 `sqlfmt explain advice plan.txt` 即可得到类似 PostgreSQL 19 输出的建议行。
pg_stash_advice 如何将建议应用到具体的查询上?
首先通过 pg_stat_statements 获取查询的 query id,然后使用 pg_create_advice_stash 创建存储区,再用 pg_set_stashed_advice 将建议字符串与 query id 关联。之后设置 pg_stash_advice.stash_name 指向该存储区,匹配 query id 的查询就会自动应用建议。
使用计划建议时需要注意哪些风险?
固定计划会剥夺规划器根据数据变化自适应调整的能力,可能导致旧计划应用于新数据,反而降低性能。因此应只保留关键建议(如 JOIN_ORDER),并谨慎使用,避免全盘固定。
计划建议与 pg_hint_plan 等传统提示方式相比有什么优势?
计划建议支持从实际计划中读取并回写,即可以保留一个经过验证的计划,而不是从头编写提示并猜测其效果。这比提示语言更简单且更实用。