DeepSeek总结的PostgreSQL 19查询提示功能

来源: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_advicepg_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 钩子的消费者,而不是规划器的并行实现。无论你对提示这一概念持何种看法,这都是一个真正的工程学胜利。

这如何回答(或不回答)六个反对理由

回顾一下第二部分的列表:

  1. 可维护性。 pg_stash_advice 将建议键与查询标识符关联,而不是嵌入在 SQL 中。你在数据库中更改建议,而不是在应用程序中。已回答。
  2. 升级干扰。 部分回答。 如果你只存储你关心的特定指令------例如 MERGE_JOIN_PLAIN(d),没有别的------规划器会继续自适应地优化其他所有内容。一个完整的、逐字固定的建议字符串会重新引入这个问题。该工具允许克制使用;它不强制执行。
  3. 助长 DBA 的坏习惯。 未回答,也无意回答。 一个求助于 pg_plan_advice 而不是修复统计信息问题的 DBA 将会得到与使用 pg_hint_plan 相同的糟糕结果。这是一个文化问题,而不是技术问题。
  4. 不随数据规模扩展。 部分回答, 原因与升级相同。有针对性的建议比完全固定计划更能承受数据增长。完全固定计划有其一直以来的问题。
  5. 通常是不必要的。 无关。这个反对意见从来就不是关于机制应该是什么样子,而只是关于机制是否应该存在。pg_plan_advice 承认它有时是必要的。
  6. 干扰规划器开发。 原始反对意见中最薄弱的一个,现在更弱了。pg_stat_statements 加上 pg_plan_advice 使得描述和报告规划器衰退变得微不足道。存储的建议是证据,而不是掩饰。

状态

该补丁尚未提交。Haas 指出"将功能纳入 v19 的时间正在迅速耗尽",并且 -hackers 的讨论一直很热烈------既有支持也有怀疑。Andrey 在博客评论中指出,pg_hint_plan 已经解决了这个问题,并且不应为了服务一个扩展已经处理的用例而使核心规划器复杂化。这不是一个边缘的反对意见。这是对 2011 年立场的合理重述,针对 2026 年进行了重申。

无论 pg_plan_advice 是落地于 19、20 还是永远不会落地,争论已经发生了变化。核心项目不再说"我们不想要这个"。它正在就具体形态进行协商。

这才是真正的新闻。

相关推荐
chenxu98b3 小时前
MySQL如何执行.sql 文件:详细教学指南
数据库·mysql
刘晨鑫13 小时前
MongoDB数据库应用
数据库·mongodb
梦想的颜色3 小时前
mongoTemplate + Java 增删改查基础介绍
数据结构·数据库·mysql
小小小米粒4 小时前
redis命令集合
数据库·redis·缓存
herinspace5 小时前
管家婆实用贴-如何分离和附加数据库
开发语言·前端·javascript·数据库·语音识别
步辞5 小时前
Go语言怎么用channel做信号通知_Go语言channel信号模式教程【完整】
jvm·数据库·python
weixin_424999366 小时前
mysql行级锁失效的原因排查_检查查询条件与执行计划
jvm·数据库·python
Polar__Star6 小时前
uni-app怎么实现App端一键换肤 uni-app全局样式动态切换【实战】
jvm·数据库·python
南境十里·墨染春水7 小时前
linux学习进展 进程间通讯——共享内存
linux·数据库·学习