展望 Postgres 19:查询提示功能
速览
PostgreSQL 19 版本计划引入查询提示(Query Hints)功能,旨在赋予开发者更细粒度的查询优化控制能力。该功能允许通过注释或特定语法向优化器提供执行建议,从而解决复杂场景下的性能瓶颈。这一更新标志着 Postgres 在查询优化灵活性上迈出了重要一步,有助于提升数据库在高负载环境下的表现。
AI 深度解读
展望 Postgres 19:查询提示(Query Hints)的正式登场
背景
PostgreSQL 社区长期以来对“查询提示”(Query Hints)持坚决的反对态度。这种立场在官方 Wiki 页面上表述得直截了当:“我们不感兴趣以其他数据库中常见的方式实现提示。基于‘因为别的数据库有’这一理由提出的提案将不受欢迎。”
社区列举了六个主要理由来解释为何反对提示:
- 它们会造成维护噩梦。
- 它们在数据库升级时容易失效。
- 它们阻碍了对根本原因的深入分析。
- 它们难以扩展。
- 优化器通常比你想象的要聪明。
- 它们实际上阻碍了规划器(Planner)的改进,因为用户不再报告 Bug,而是直接通过提示绕过问题。
然而,这种“坚定”的立场背后并非没有争议。早在 2010 年底,pgsql-performance 邮件列表上就爆发了一场长达数月的激烈辩论。争论始于对慢速 COUNT(*) 查询的抱怨,随后演变为关于 PostgreSQL 是否需要提示的生存危机式讨论。
尽管社区官方立场强硬,但 DBA 和开发者在实际操作中早已通过“非正式”的方式使用提示。例如,通过设置 enable_seqscan = off 强制索引扫描,或在子查询中添加 OFFSET 0 以防止规划器将其扁平化,甚至使用物化 CTE 作为优化围栏。第三方扩展 pg_hint_plan 也填补了这一空白,允许用户通过注释方式嵌入类似 Oracle 的提示语法。
直到 PostgreSQL 19 的功能冻结期,这一长期争论才迎来了转折点:由传奇开发者 Robert Haas 主导的两个新 contrib 模块 pg_plan_advice 和 pg_stash_advice 正式引入。这标志着 PostgreSQL 终于以官方认可的方式,接纳了查询优化建议机制。
核心内容
PostgreSQL 19 并没有直接引入传统意义上嵌入 SQL 注释的“提示”,而是引入了两个新的 contrib 模块:pg_plan_advice 和 pg_stash_advice。社区刻意避开了“Hints”这一术语,转而使用“Plan Advice”(计划建议),以体现其设计理念的不同。
设计哲学:解决痛点而非复制 Oracle
Robert Haas 作为该功能的开发者,深谙过去二十年社区反对提示的理由,并针对性地设计了新机制:
-
解耦 SQL 文本: 针对 Josh Berkus 等人提出的“提示不应嵌入查询文本以免增加维护负担”的观点,
pg_plan_advice将建议完全独立于 SQL 语句之外。建议可以通过 GUC 参数(pg_plan_advice.advice)设置,或存储在基于查询 ID 的独立 stash 中。查询本身保持纯净,不受额外语法污染。 -
约束而非替代: 针对“提示会取代优化器判断”的担忧,新机制并非强制执行特定计划,而是约束优化器的搜索空间。文档明确指出,建议“只能产生核心规划器认为可行的计划”。建议的作用是将优化器引导至它已经考虑过的某个计划,而不是强行执行一个它认为不可行的计划。
-
优雅降级: 如果建议错误、过时或与当前数据分布冲突,系统不会静默生成垃圾计划,也不会直接报错崩溃。相反,优化器会将受影响的节点标记为
Disabled,并在剩余约束下回退到它认为最佳的计划。这种设计确保了系统的鲁棒性。 -
自动生成建议: 用户无需记忆复杂的语法或手动编写建议字符串。优化器可以生成当前的执行计划建议。只需在
EXPLAIN命令中添加PLAN_ADVICE选项,优化器就会返回一段可直接作为建议使用的语法字符串。
技术实现与语法示例
通过启用 pg_plan_advice 到 shared_preload_libraries,用户可以轻松获取当前计划的建议格式。
示例:
CREATE TABLE my_fact (
id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
dim_id BIGINT NOT NULL
);
CREATE TABLE my_dim (
id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
stuff TEXT
);
CREATE INDEX idx_fact_dim_id ON my_fact (dim_id);
EXPLAIN (COSTS OFF, PLAN_ADVICE)
SELECT * FROM my_fact f
JOIN my_dim d ON f.dim_id = d.id;
输出结果:
QUERY PLAN
----------------------------------
Hash Join
Hash Cond: (f.dim_id = d.id)
-> Seq Scan on my_fact f
-> Hash
-> Seq Scan on my_dim d
Generated Plan Advice:
JOIN_ORDER(f d)
HASH_JOIN(d)
SEQ_SCAN(f d)
NO_GATHER(f d)
优化器直接给出了复现当前计划的建议字符串。用户可以根据需要调整这些参数,将其应用于生产环境。
建议语言的表达能力
尽管社区曾长期抵制,但新的建议语言 surprisingly expressive(令人惊讶地富有表现力),涵盖了多个优化维度:
-
扫描方法控制: 可以指定特定表的访问方式。例如,强制使用特定索引:
SET pg_plan_advice.advice = 'INDEX_SCAN(f idx_fact_dim_id)';甚至支持
DO_NOT_SCAN以在最终计划中完全排除某张表(通常用于测试或特定优化场景)。 -
连接顺序控制:
JOIN_ORDER标签支持嵌套语法,提供严格的顺序控制或灵活的分组:- 严格顺序:
JOIN_ORDER(a (b c) d)表示先连接 b 和 c,然后将结果与 a 连接,最后与 d 连接。 - 灵活分组:
JOIN_ORDER(a {b c} d)表示 b 和 c 可以以任意顺序连接,但它们整体必须在 a 之后、d 之前连接。
- 严格顺序:
关键要点
- 官方接纳:PostgreSQL 19 正式引入了查询优化建议机制,结束了长达数十年的社区争论。
- 非传统提示:新特性名为“Plan Advice”,通过
pg_plan_advice和pg_stash_advice模块实现,而非直接嵌入 SQL 注释。 - 安全性与鲁棒性:建议仅约束优化器的搜索空间,不强制执行不可行计划;若建议失效,系统会优雅降级回退到默认优化结果。
- 自动化生成:支持通过
EXPLAIN (PLAN_ADVICE)自动生成建议字符串,降低了用户手动编写和维护建议的成本。 - 解耦设计:建议存储在 GUC 参数或基于查询 ID 的 stash 中,保持 SQL 文本的纯净,便于版本控制和升级。
- 精细控制:支持控制扫描方法(如
INDEX_SCAN)、连接顺序(JOIN_ORDER)等底层优化细节。 - 开发者背景:该功能由 PostgreSQL 核心开发者 Robert Haas 开发,他亲自参与了早期的争论,并针对性地解决了社区提出的所有主要反对意见。
意义与影响
1. 弥合社区分歧,确立务实路线
PostgreSQL 社区以“优化器足够智能”和“反对黑魔法”著称。引入 pg_plan_advice 标志着社区在坚持核心哲学(优化器主导)与满足 DBA 实际需求(需要兜底手段)之间找到了平衡点。它承认了极端边缘情况(0.1% 的查询)的存在,并为 DBA 提供了在不修改代码的情况下应对这些情况的合法途径。
2. 降低调优门槛,提升可维护性
传统的 pg_hint_plan 扩展需要用户手动编写和维护提示字符串,容易在数据库升级或表结构变更时失效。新的机制通过自动生成建议和基于查询 ID 的存储,大幅降低了维护成本。DBA 无需成为优化器专家即可通过可视化的建议调整性能,同时避免了硬编码带来的技术债务。
3. 增强系统可控性,而非削弱优化器
与 Oracle 等数据库强制执行提示不同,PostgreSQL 的新机制保留了优化器的最终决策权。这种“建议”而非“命令”的模式,确保了数据库在数据分布
