来源:https://thebuild.com/blog/2026/04/21/hints-part-3-advice-not-orders/
提示,第三部分:建议,而非命令
我对 pg_plan_advice 早期预览版的反应。
Robert Haas 的 pg_plan_advice 补丁集(提议用于 PostgreSQL 19)是第二部分中那场持续了二十年的争论所达成的(或者说试图达成的)结果。它不是将 pg_hint_plan 引入核心。它是一个不同的东西,具有不同的机制、不同的范围,并且对"这与 Oracle 风格的提示有何不同"这个问题给出了不同的答案。
该补丁引入了三个 contrib 模块:
pg_plan_advice------ 核心机制。通过EXPLAIN生成计划建议字符串,并通过一个 GUC 来应用它们。pg_collect_advice------ 一个示例扩展,展示了如何扩展建议收集功能。pg_stash_advice------ 一个示例扩展,展示了如何扩展建议应用功能,通过查询标识符将存储的建议字符串与查询匹配。
这种三部分拆分很重要。pg_plan_advice 被刻意设计得极简------Haas 将这种设计描述为"机制,而非策略"------而另外两个模块则演示了你可以在此基础上构建什么。该架构假设其他扩展将完成有趣的工作。
生成建议
加载扩展并使用新的 PLAN_ADVICE 选项运行 EXPLAIN:
sql
LOAD 'pg_plan_advice';
EXPLAIN (COSTS OFF, PLAN_ADVICE)
SELECT * FROM join_fact f JOIN join_dim d ON f.dim_id = d.id;
QUERY PLAN
------------------------------------
Hash Join
Hash Cond: (f.dim_id = d.id)
-> Seq Scan on join_fact f
-> Hash
-> Seq Scan on join_dim d
Generated Plan Advice:
JOIN_ORDER(f d)
HASH_JOIN(d)
SEQ_SCAN(f d)
NO_GATHER(f d)
"Generated Plan Advice" 部分就是新的输出。它以一种紧凑、结构化的形式描述了计划形态:连接顺序、连接方法、每个表的扫描方法、并行决策。这就是建议字符串。
应用建议
通过一个 GUC 将建议交还给规划器:
sql
BEGIN;
SET LOCAL pg_plan_advice.advice = 'HASH_JOIN(d)';
EXPLAIN (COSTS OFF, PLAN_ADVICE)
SELECT * FROM join_fact f JOIN join_dim d ON f.dim_id = d.id;
规划器产生相同的哈希连接计划,并且 EXPLAIN 输出现在在生成的建议旁边包含一个"Supplied Plan Advice"部分,用 /* matched */ 或未匹配注释每个提供的指令。你可以准确地看到规划器采纳了你的哪些建议。
你不必传回完整的建议字符串。只关心你关心的部分也可以。如果你只想固定连接方法,而让规划器决定其他一切,只需传递 HASH_JOIN(d)。
强制不同的计划
改变建议字符串以强制不同的计划形态:
sql
SET LOCAL pg_plan_advice.advice = 'MERGE_JOIN_PLAIN(d)';
EXPLAIN (COSTS OFF, PLAN_ADVICE)
SELECT * FROM join_fact f JOIN join_dim d ON f.dim_id = d.id;
QUERY PLAN
----------------------------------------------------------------
Merge Join
Merge Cond: (f.dim_id = d.id)
-> Index Scan using join_fact_dim_id on join_fact f
-> Index Scan using join_dim_pkey on join_dim d
Supplied Plan Advice:
MERGE_JOIN_PLAIN(d) /* matched */
Generated Plan Advice:
JOIN_ORDER(f d)
MERGE_JOIN_PLAIN(d)
INDEX_SCAN(f public.join_fact_dim_id d public.join_dim_pkey)
NO_GATHER(f d)
注意生成的建议是如何更新以反映新计划的。改变连接方法,扫描方法也随之改变------规划器现在选择索引扫描以为合并连接提供排序输入。建议机制强制执行你指定的选择,而规划器仍然围绕这些选择优化其他所有内容。
建议词汇表
从回归测试和 Haas 的博客文章来看,当前的指令包括:
JOIN_ORDER(rel1 rel2 ...)------ 命名关系的连接顺序HASH_JOIN(rel),MERGE_JOIN_PLAIN(rel)------ 给定关系的连接方法SEQ_SCAN(rel),INDEX_SCAN(rel idx ...)------ 扫描方法,带有可选的索引规范NO_GATHER(rel rel ...)------ 禁用命名关系的并行 gather
这比 Oracle 或 pg_hint_plan 支持的范围要窄。明显缺失的包括:基数覆盖、行数注入、规划器成本覆盖、返回集合函数的提示、物化指令。其表面积是故意做小的。这是 1.0 版本,Haas 对此很坦诚------"有很多限制"和"一些仍然相当笨拙的东西"。预计词汇表会增长。
pg_stash_advice:不触碰查询的建议
为每个查询设置一个 GUC 对于交互式调试来说没问题,但对于生产环境来说毫无用处。这就是 pg_stash_advice 的用途:
sql
ALTER SYSTEM SET shared_preload_libraries = 'pg_stash_advice';
ALTER SYSTEM SET pg_stash_advice.stash_name = 'my_stash';
SELECT pg_create_advice_stash('my_stash');
SELECT pg_set_stashed_advice('my_stash', :qid, 'MERGE_JOIN_PLAIN(d)');
重新加载后,每次在 pg_stash_advice.stash_name 匹配的会话中规划具有该查询标识符的查询时,存储的建议都会自动应用。你没有触碰 SQL。你没有触碰应用程序。你从一个具有适当权限的数据库角色固定了计划。
这是 SQL Server 的计划指南模式和 Db2 的优化配置文件模式,在它们出现近二十年后才落地 PostgreSQL。
存储库通过查询标识符 来键------就是你在 pg_stat_statements 中看到的那个标识符。因此工作流程自然形成:通过 pg_stat_statements 找到一个慢查询,通过 EXPLAIN (PLAN_ADVICE) 为你想要的计划获取建议,将其存储到查询 id 下,然后就不用管了。
如果 pg_plan_advice.advice 和存储的建议条目同时适用于一个查询,则会话级别的设置获胜。存储的建议是基线;会话设置是覆盖。
机制,而非策略
Haas 对该架构的表述很明确:pg_plan_advice 是可插拔的核心,而 pg_stash_advice 和 pg_collect_advice 是演示可插拔性让你能构建什么的示例扩展。pg_stash_advice 通过查询标识符键并在动态共享内存中存储,这是一个合理的默认值,并不是因为它唯一正确的设计。一个不同的扩展可以通过你计算的查询哈希、通过规范化查询文本的正则表达式、通过用户或角色、通过任何方式来键。一个不同的扩展可以将建议存储在目录表、etcd、sidecar 服务中。
这就是为什么 pg_plan_advice 不是将 pg_hint_plan 引入核心。pg_hint_plan 是一个完整的、有主见的解决方案。pg_plan_advice 是一个工具包。
实际的结果是,一旦这个落地,pg_hint_plan 本身就可以减少大量的代码。它目前为了实现其功能而复制了规划器的大部分内容;新的基础设施可以让大部分重复代码消失。pg_hint_plan 将成为 pg_plan_advice 钩子的消费者,而不是规划器的并行实现。无论你对提示这一概念持何种看法,这都是一个真正的工程学胜利。
这如何回答(或不回答)六个反对理由
回顾一下第二部分的列表:
- 可维护性。
pg_stash_advice将建议键与查询标识符关联,而不是嵌入在 SQL 中。你在数据库中更改建议,而不是在应用程序中。已回答。 - 升级干扰。 部分回答。 如果你只存储你关心的特定指令------例如
MERGE_JOIN_PLAIN(d),没有别的------规划器会继续自适应地优化其他所有内容。一个完整的、逐字固定的建议字符串会重新引入这个问题。该工具允许克制使用;它不强制执行。 - 助长 DBA 的坏习惯。 未回答,也无意回答。 一个求助于
pg_plan_advice而不是修复统计信息问题的 DBA 将会得到与使用pg_hint_plan相同的糟糕结果。这是一个文化问题,而不是技术问题。 - 不随数据规模扩展。 部分回答, 原因与升级相同。有针对性的建议比完全固定计划更能承受数据增长。完全固定计划有其一直以来的问题。
- 通常是不必要的。 无关。这个反对意见从来就不是关于机制应该是什么样子,而只是关于机制是否应该存在。
pg_plan_advice承认它有时是必要的。 - 干扰规划器开发。 原始反对意见中最薄弱的一个,现在更弱了。
pg_stat_statements加上pg_plan_advice使得描述和报告规划器衰退变得微不足道。存储的建议是证据,而不是掩饰。
状态
该补丁尚未提交。Haas 指出"将功能纳入 v19 的时间正在迅速耗尽",并且 -hackers 的讨论一直很热烈------既有支持也有怀疑。Andrey 在博客评论中指出,pg_hint_plan 已经解决了这个问题,并且不应为了服务一个扩展已经处理的用例而使核心规划器复杂化。这不是一个边缘的反对意见。这是对 2011 年立场的合理重述,针对 2026 年进行了重申。
无论 pg_plan_advice 是落地于 19、20 还是永远不会落地,争论已经发生了变化。核心项目不再说"我们不想要这个"。它正在就具体形态进行协商。
这才是真正的新闻。