编程 PostgreSQL 19 深度前瞻:原生图查询、零停机 REPACK 与并行 Autovacuum,2026 年最值得升级的数据库大版本

2026-07-24 00:44:08 +0800 CST views 6

PostgreSQL 19 深度前瞻:原生图查询、零停机 REPACK 与并行 Autovacuum,2026 年最值得升级的数据库大版本

2026 年 4 月 8 日,PostgreSQL 19 正式进入特性冻结(Feature Freeze),Beta 已经放出,正式版锁定 2026 年 9 月。这一版的分量,可能是最近五年里最重的一次:SQL/PGQ 原生图查询进内核、ON CONFLICT DO SELECT 补齐 Upsert 最后一块拼图、REPACK CONCURRENTLY 让 DBA 告别凌晨三点锁表、autovacuum 终于学会并行干活、64 位 MultiXact 干掉一个折磨了社区十几年的"要么 vacuum 要么死"的故障模式。

这篇文章不做新闻搬运。我会按"这个特性解决什么真实痛点 → 底层怎么实现 → 生产上怎么用 → 有什么坑"的思路,把 PG 19 值得你关注的东西一个一个拆开讲透。

一、先说结论:谁应该关注 PG 19

如果你符合以下任意一条,PG 19 对你来说不是"可升可不升",而是"值得排进 2026 Q4 的规划":

  • 业务里有图状关系查询(社交关系、风控图谱、推荐链路、权限继承树),目前靠递归 CTE 硬写,或者额外维护了一套 Neo4j —— SQL/PGQ 可能让你把图数据库直接下线。
  • 有高并发 Upsert 场景(去重表、幂等写入、"获取或创建"模式)—— ON CONFLICT DO SELECT 把原来要两条 SQL + 重试循环的逻辑压缩成一条原子语句。
  • 被表膨胀折磨过,做过 pg_repack,或者半夜执行过 VACUUM FULL —— REPACK CONCURRENTLY 是内核原生的在线表重组,不再需要外部扩展。
  • 大表 autovacuum 追不上写入速度 —— 并行 autovacuum + 表优先级机制直接命中这个痛点。
  • 凌晨被 MultiXact 成员耗尽的告警叫醒过 —— 64 位 MultiXact 让这个故障模式成为历史。

下面逐个拆。

二、SQL/PGQ:关系型数据库的"图思维"革命

2.1 这是什么,为什么重要

PG 19 最受瞩目的特性,是对 SQL/PGQ(Property Graph Queries,ISO SQL:2023 第 16 部分) 的原生支持。一句话概括:你可以把现有的关系表声明为一张属性图,然后用图模式匹配语法直接遍历它,不需要迁移数据,不需要额外部署图数据库。

在此之前,PostgreSQL 里做图遍历只有两条路:

  1. 递归 CTEWITH RECURSIVE):能用,但表达"变长路径 + 多种边类型 + 环检测"时,SQL 会膨胀成一堵没人敢碰的墙。
  2. 外挂图数据库(Neo4j、NebulaGraph 等):引入了双写一致性、数据同步延迟、多一套运维体系三座大山。

SQL/PGQ 的思路是"图是关系数据的一种视图"。你的点(vertex)和边(edge)本来就存在普通表里,图定义只是给它们贴上语义标签。

2.2 上手:从建表到图查询

先准备一个典型的社交场景——用户和关注关系:

CREATE TABLE users (
    id     bigint PRIMARY KEY,
    name   text NOT NULL,
    city   text
);

CREATE TABLE follows (
    follower_id  bigint REFERENCES users(id),
    followee_id  bigint REFERENCES users(id),
    since        date,
    PRIMARY KEY (follower_id, followee_id)
);

在 PG 19 里,把这两张表声明为一张属性图:

CREATE PROPERTY GRAPH social_graph
  VERTEX TABLES (
    users KEY (id)
      LABEL person
      PROPERTIES (id, name, city)
  )
  EDGE TABLES (
    follows KEY (follower_id, followee_id)
      SOURCE KEY (follower_id) REFERENCES users (id)
      DESTINATION KEY (followee_id) REFERENCES users (id)
      LABEL follows
      PROPERTIES (since)
  );

注意几个关键点:

  • VERTEX TABLES / EDGE TABLES 只是元数据声明,不复制数据,不建新存储。底层还是那两张堆表。
  • 边表必须声明 SOURCEDESTINATION,本质上就是把外键关系升格为图语义。
  • 一张表可以挂多个 LABEL,同一个图里可以有多种点和边。

然后就是重头戏,GRAPH_TABLE 查询——找出"张三关注的人所关注的人"(二度人脉):

SELECT gt.*
FROM GRAPH_TABLE (
  social_graph
  MATCH (a IS person WHERE a.name = '张三')
        -[IS follows]-> (b IS person)
        -[IS follows]-> (c IS person)
  WHERE c.id <> a.id
  COLUMNS (c.name AS fof_name, c.city AS fof_city)
) gt;

对比一下等价的递归 CTE 写法(这还只是二度,固定深度都这么啰嗦):

SELECT DISTINCT u3.name, u3.city
FROM users u1
JOIN follows f1 ON f1.follower_id = u1.id
JOIN follows f2 ON f2.follower_id = f1.followee_id
JOIN users u3 ON u3.id = f2.followee_id
WHERE u1.name = '张三' AND u3.id <> u1.id;

二度还能忍。但当需求变成"3 到 5 度以内、途经至少一个'认证用户'、路径上不许出现被拉黑的边",JOIN 写法直接爆炸,而图模式语法只是在 MATCH 里多写几个量词和条件。

2.3 实现原理与性能预期

理解 SQL/PGQ 在 PG 19 里的执行方式非常重要,因为它决定了你的性能预期:

GRAPH_TABLE 在解析阶段被重写为普通的 JOIN 树。 也就是说,图查询最终走的还是 PostgreSQL 那套成熟的优化器和执行器——该用 Hash Join 用 Hash Join,该走索引走索引。

这带来两个直接推论:

  1. 好消息:你之前所有的调优经验都有效。边表上 (follower_id)(followee_id) 两个方向的索引照建,work_mem、并行查询照调,EXPLAIN 照看。
  2. 要清醒的地方:它不是 Neo4j 那种以邻接遍历为原生存储模型的引擎。超深路径(十几跳)、超大规模全图算法(PageRank、社区发现)依然不是它的主场。PG 19 的 SQL/PGQ 首发版本主要覆盖固定/有限深度的模式匹配,定位是干掉"80% 的轻中度图需求"——恰恰是大多数业务实际需要的那部分。

我的判断:对于"主数据在 PG、图需求是查询而非全图计算"的团队,SQL/PGQ 落地后,单独的图数据库会越来越难以证明自己的存在必要。少一套存储、少一条同步链路、事务原生一致,这三条在架构评审上几乎是降维打击。

三、ON CONFLICT DO SELECT:Upsert 拼图的最后一块

3.1 "获取或创建"的老大难

这个需求每个后端工程师都写过一百遍:插入一行,如果已经存在,就把已存在的那行拿回来。典型场景:根据 email 拿用户 ID、标签去重入库、幂等事件登记。

PG 19 之前的主流写法是这样:

INSERT INTO tags (name) VALUES ('postgres')
ON CONFLICT (name) DO UPDATE SET name = EXCLUDED.name
RETURNING id;

看出别扭了吗?为了拿回已存在行的 id,你被迫做了一次假更新——SET name = EXCLUDED.name 把值更新成它自己。这个 hack 的代价是真实的:

  • 产生一个新的行版本(dead tuple),白白制造表膨胀;
  • 触发行级锁和 WAL 写入,高并发下是实打实的开销;
  • 会触发 ON UPDATE 触发器,可能引爆意料之外的业务逻辑。

另一派写法是先 SELECTINSERT ... ON CONFLICT DO NOTHINGSELECT,三条语句加重试循环,代码丑且仍有竞态窗口要小心处理。

3.2 PG 19 的答案

INSERT INTO tags (name) VALUES ('postgres')
ON CONFLICT (name) DO SELECT
RETURNING id, name;

语义非常干净:插入成功返回新行;冲突则直接返回已存在的行,不产生任何写放大。没有假更新、没有 dead tuple、没有触发器副作用。

更关键的是它继承了 ON CONFLICT 家族的核心承诺:无竞态。与 SQL 标准的 MERGE 不同,ON CONFLICT 在并发场景下保证操作结果必然是"插入"或"取回"之一,不会因为并发事务的插入/删除时序问题而报错或丢失。这就是为什么 PostgreSQL 社区在已有 MERGE 的情况下仍然坚持扩展 ON CONFLICT——两者的并发语义根本不同。

还可以带锁语义,配合后续更新:

-- 拿回已存在行并锁住它,防止后续事务修改
INSERT INTO accounts (email) VALUES ('dev@example.com')
ON CONFLICT (email) DO SELECT FOR UPDATE
RETURNING id;

3.3 生产建议

  • 存量代码里所有 DO UPDATE SET x = EXCLUDED.x 的假更新写法,升级后应该系统性地替换为 DO SELECT。在写入热点表上,这能直接降低膨胀速率和 WAL 量。
  • ORM 层面(SQLAlchemy、Prisma、GORM 等)预计会在 PG 19 GA 后跟进适配,但在那之前用原生 SQL 完全值得。

四、REPACK CONCURRENTLY:告别凌晨三点的锁表窗口

4.1 表膨胀,DBA 的慢性病

PostgreSQL 的 MVCC 决定了 UPDATE/DELETE 不会立即释放空间,死元组靠 vacuum 回收。但 vacuum 只能把空间标记为可复用,无法把已经膨胀的物理文件缩回去。一张频繁更新的 500GB 表,实际有效数据可能只有 150GB,剩下 350GB 是碎片——白吃磁盘、白吃 buffer cache、拖慢顺序扫描。

治疗手段一直很尴尬:

  • VACUUM FULL:重写全表,但持有 ACCESS EXCLUSIVE 锁,锁表期间业务完全中断,大表意味着几十分钟到几小时的停机;
  • pg_repack 扩展:能在线做,但是外部工具,需要额外安装、有版本兼容矩阵、出问题时的排障资料远不如内核特性丰富。

4.2 内核原生方案

PG 19 引入 REPACK 命令,并且支持 CONCURRENTLY

-- 在线重组表,业务读写照常进行
REPACK TABLE CONCURRENTLY my_big_table;

-- 按某个索引的顺序重排物理存储(类似 CLUSTER,但在线)
REPACK TABLE CONCURRENTLY my_big_table USING INDEX idx_created_at;

实现思路可以概括为"新文件重建 + 逻辑解码追增量":

  1. 给表打快照,把有效数据顺序写入一个全新的物理文件(这一步顺带完成排序和碎片消除);
  2. 重建期间业务产生的增量变更,通过逻辑解码机制持续回放到新文件;
  3. 增量追平后,在一个极短的锁窗口内完成新旧文件切换。

整个过程中只在最后切换那一瞬间需要短暂加锁,对业务的感知接近于零。这套思路和 pg_repack 的触发器方案、以及 MySQL 的 gh-ost 是同一个流派,但由内核原生实现,意味着与 WAL、复制、备份工具链的兼容性从根上就是对齐的。

4.3 注意事项

  • REPACK 期间磁盘上会同时存在新旧两份文件,峰值需要约 2 倍表大小的空闲空间,容量规划要提前做;
  • 逻辑解码追增量的机制决定了:写入极端热的表,追平窗口会拉长,建议避开写高峰执行;
  • pg_repack 迁移过来的团队,可以逐步把 cron 里的外部工具调用替换为内核命令,少维护一个扩展的兼容矩阵。

五、Autovacuum 的两次进化:并行 + 优先级

5.1 并行 autovacuum:解除了十几年的封印

手动 VACUUM (PARALLEL 4) 早在 PG 13 就支持并行清理索引,但 autovacuum 一直被禁止使用并行——这是个历史遗留的保守设定。结果就是:一张有十几个大索引的表,autovacuum 串行逐个清理索引,慢到追不上死元组的产生速度,膨胀风险持续累积。

PG 19 新增:

-- 全局:允许 autovacuum 最多使用 4 个并行 worker 清理索引
SET autovacuum_max_parallel_workers = 4;

-- 表级:针对索引多的大表单独放开
ALTER TABLE orders SET (parallel_workers = 4);

收益最大的场景就是"宽表 + 多索引":索引清理是 vacuum 全程最慢的阶段,并行化之后整轮 vacuum 的墙钟时间可以数倍缩短。

一个必须注意的坑:每个并行 worker 会占用自己的 maintenance_work_mem 份额。如果你把 maintenance_work_mem 调得很大又开了多个并行 worker,内存峰值是乘法关系,容易把机器打爆。升级后要把这两个参数放在一起重新核算。

5.2 表优先级:谁急谁先来

老版本 autovacuum 挑表的顺序基本是"按发现顺序",没有轻重缓急的概念。经典惨案:一张不太要紧的日志表占着 worker 慢慢清,核心订单表眼睁睁膨胀到触发事务 ID 告警。

PG 19 引入了 autovacuum 优先级机制,配套 pg_stat_autovacuum_scores 视图,让你能直接看到每张表在调度器眼里的"焦虑分数":

SELECT relname, score, last_autovacuum
FROM pg_stat_autovacuum_scores
ORDER BY score DESC
LIMIT 10;

膨胀风险第一次变成了可观测、可排序、可干预的指标,而不是靠 DBA 的经验和几段第三方监控 SQL 拼凑。

5.3 64 位 MultiXact:干掉一个凌晨三点级故障

这个改进不显眼,但老运维看到会直接鼓掌。

背景:当多个事务同时持有同一行的共享锁(SELECT ... FOR SHARE、外键约束检查都会触发),PostgreSQL 用 MultiXact 结构记录"哪些事务锁了这行"。而 MultiXact 的成员计数器是 32 位的——高并发外键写入场景下,40 亿的成员空间是真的能耗尽的。耗尽之后系统拒绝新事务,唯一恢复路径是停应用、做紧急 VACUUM。这就是社区流传的"要么 vacuum,要么死"(vacuum-or-die)故障模式。

PG 19 把成员计数器扩展到 64 位。理论上回卷仍然存在,但 2^64 的空间在实践中意味着这个故障模式彻底消失。如果你没被它坑过,恭喜;如果被坑过,你知道这一条就够你升级了。

六、查询规划提示(Plan Hints):官方终于松口了

PostgreSQL 社区拒绝 hint 拒绝了二十年,官方立场一直是"优化器应该被修好,而不是被绕过"。第三方的 pg_hint_plan 因此成了很多生产环境的必装扩展。

PG 19 的变化:官方 contrib 模块提供计划提示能力。注意两个措辞——"contrib"和"提示":

  • 放在 contrib 而不是内核默认行为,表明社区的态度依然克制:这是应急工具,不是常规武器;
  • 典型使用场景是"优化器在某个统计信息失真的边角案例里选了灾难性计划,业务等不起统计修复周期"——这时候一个 hint 能救命。

我的建议和社区一致:hint 是止血带,不是日常穿搭。每一个上了 hint 的查询都应该带着注释和工单号,定期回顾能不能摘掉。但"官方提供止血带"本身,是对生产现实的重要让步,值得肯定。

七、其他值得记一笔的改进

时态数据操作(Temporal DML):跟进 SQL:2011 时态标准,对带有效期的数据(价格历史、政策版本)提供原生的时段更新/删除语义,不用再手写"截断旧区间 + 插入新区间"的三段式 SQL。

逻辑复制持续补强:无需重启即可启用 WAL 逻辑解码(wal_level 调整不再需要重启窗口,这对 7×24 业务是重大利好)、复制槽同步延迟监控。逻辑复制正在从"能用"走向"运维友好"。

原生 JSON 导出pg_dump 家族和 COPY 链路对 JSON 导出的原生支持,数据交换管道少写一层转换脚本。

监控颗粒度:vacuum/analyze 进度视图带上内存使用信息、pg_get_multixact_stats() 等新观测函数——配合前面的优先级分数视图,PG 19 的可观测性是全方位加密的。

jsonb_agg 优化、LISTEN/NOTIFY 性能增强、file_fdw 跳过起始行:都是小刀,但都砍在日常使用的高频路径上。

八、升级实操清单

结合 Beta 阶段已知信息,给一份务实的升级准备清单:

兼容性排查(现在就可以做):

  1. standard_conforming_strings 在 PG 19 被永久固定为 on——还依赖老式反斜杠转义行为的祖传代码(十几年前的 PHP 项目重灾区)需要排查;
  2. 检查所有假更新式 Upsert(DO UPDATE SET x = EXCLUDED.x),列入 DO SELECT 改造清单;
  3. 用了 pg_repack 的,评估切换到原生 REPACK CONCURRENTLY 的窗口;
  4. 用了 pg_hint_plan 的,关注官方 contrib hint 模块的语法差异。

参数重新核算:

-- 升级后建议的初始核算项
autovacuum_max_parallel_workers  -- 从 0 开始逐步放开,观察 IO
maintenance_work_mem             -- 与并行 worker 数相乘核算峰值内存

灰度节奏建议:9 月 GA 后,先上报表库/只读副本 → 观察一个完整业务周期 → 再动核心 OLTP。PG 的 .0 版本质量一贯很高,但大版本升级的谨慎永远不多余。

九、总结:PG 19 在下一盘什么棋

把 PG 19 的特性列表放远一点看,能看出 PostgreSQL 社区清晰的战略:

  1. 吞并邻近领域:pgvector 吃向量数据库的份额,SQL/PGQ 吃图数据库的份额。"一个 PostgreSQL 顶过去四五个专用存储"的叙事正在一个版本一个版本地兑现。
  2. 消灭停机窗口:在线 REPACK、免重启逻辑解码——所有需要"挑时间做"的运维动作都在被改造成"随时能做"。这是云时代数据库的生存底线。
  3. 把 DBA 的经验固化成机制:autovacuum 优先级分数、并行清理、64 位 MultiXact,全是把"资深 DBA 半夜救火的经验"变成"内核默认就做对的事"。

对开发者,ON CONFLICT DO SELECT 和图查询会直接改变你写代码的方式;对 DBA,这可能是历年来"减少半夜告警"含金量最高的一个版本。

2026 年 9 月,值得把一次升级排进日程表。


本文基于 PostgreSQL 19 Beta 及社区公开资料撰写,个别特性细节以 GA 版 Release Notes 为准。

推荐文章

使用 sync.Pool 优化 Go 程序性能
2024-11-19 05:56:51 +0800 CST
Vue3中的v-model指令有什么变化?
2024-11-18 20:00:17 +0800 CST
如何使用go-redis库与Redis数据库
2024-11-17 04:52:02 +0800 CST
如何在Vue3中处理全局状态管理?
2024-11-18 19:25:59 +0800 CST
Vue3 结合 Driver.js 实现新手指引
2024-11-18 19:30:14 +0800 CST
Go 单元测试
2024-11-18 19:21:56 +0800 CST
向满屏的 Import 语句说再见!
2024-11-18 12:20:51 +0800 CST
在 Nginx 中保存并记录 POST 数据
2024-11-19 06:54:06 +0800 CST
程序员茄子在线接单