编程 PostgreSQL 19 Beta 1 深度解读:当图查询遇见关系数据库——从 SQL/PGQ 到 REPACK,DBA 必须掌握的 12 个新特性

2026-06-17 15:53:26 +0800 CST views 5

PostgreSQL 19 Beta 1 深度解读:当图查询遇见关系数据库——从 SQL/PGQ 到 REPACK,DBA 必须掌握的 12 个新特性

一、写在前面:一个里程碑版本

2026 年 6 月 4 日,PostgreSQL Global Development Group 正式发布了 PostgreSQL 19 Beta 1。

如果你以为这只是一个 "例行年度大版本",那就错过了太多。PG19 可能是自 PG12 以来,对运维体验冲击最大的一个版本——不是因为 SQL/PGQ 图查询这个头条特性,而是一系列被掩盖在聚光灯下的底层变革:64 位 MultiXact 终结了困扰 DBA 十年的 "要么 VACUUM 要么死" 噩梦,REPACK CONCURRENTLY 让在线表重组不再是奢侈品,并行 autovacuum 让索引密集表的维护真正跑了起来,而 JIT 默认关闭则悄无声息地救回了一大批 OLTP 系统的尾延迟。

这篇文章不会变成官方 Release Notes 的翻译。我会以一个实际使用 PostgreSQL 的开发者/DBA 视角,深入剖析 PG19 中真正影响你生产系统的 12 个核心变化,每个特性都附带代码示例、架构分析和避坑指南。


二、SQL/PGQ:当 PostgreSQL 变成 "图数据库"

2.1 背景:为什么关系数据库需要图查询?

传统上,社交网络、推荐系统、知识图谱这类场景会把你引向 Neo4j、JanusGraph 等专用图数据库。但多一个存储系统就多一套运维负担——数据同步、跨库 JOIN、事务一致性都成了头疼的问题。

SQL/PGQ(ISO/IEC 9075-16:2023)标准正是为了解决这个痛点:在关系表上直接定义属性图,用类 Cypher 语法做图遍历,底层存储还是那张表

2.2 怎么玩?一个完整的例子

假设你有一个社交网络应用:

-- PG19 之前:你得写多层嵌套 JOIN
SELECT DISTINCT f2.followed_id
FROM follows f1
JOIN follows f2 ON f1.followed_id = f2.follower_id
WHERE f1.follower_id = 1;

这在三层时还能忍,到了六度关系就彻底没法看了。PG19 的方案是:

-- Step 1: 在现有表上定义属性图
CREATE PROPERTY GRAPH social_graph
  VERTEX TABLES (
    users LABEL person PROPERTIES (id, name, age)
  )
  EDGE TABLES (
    follows LABEL follows
      SOURCE KEY (follower_id) REFERENCES users (id)
      DESTINATION KEY (followed_id) REFERENCES users (id)
      PROPERTIES (created_at)
  );

-- Step 2: 用类 Cypher 语法查询
-- 查找 Alice 的朋友的朋友
SELECT a.name AS person, ff.name AS friend_of_friend
FROM GRAPH_TABLE (
  social_graph
  MATCH (a IS person WHERE a.name = 'Alice')
        -[IS follows]->(b IS person)
        -[IS follows]->(ff IS person)
  WHERE a.id <> ff.id
) AS g;

2.3 性能如何?

这里有一个容易被忽略的点:SQL/PGQ 的底层执行计划仍然是 PostgreSQL 的查询优化器在生成。换句话说,你得到的不是 "图数据库引擎",而是 PostgreSQL 查询优化器对图模式匹配语法的编译。

这意味着:

  • 现有索引完全可用:如果 follower_idfollowed_id 上有 B-tree 索引,SQL/PGQ 查询会走索引扫描
  • JOIN 顺序由优化器决定:你不需要手动调整表连接顺序
  • 并行查询仍生效:多深度遍历可以并行执行
  • 局限性:没有原生的图遍历算法(如 PageRank、LPA 社团发现),这部分仍需用 pg_analytics 或外部工具

2.4 生产建议

适合的场景

  • 社交关系、推荐系统、权限继承树、组织架构
  • 供应链上下游追踪
  • 知识图谱在 OLTP 场景下的轻量查询

不适合的场景

  • 十层以上的深度遍历(优化器可能退化)
  • 需要 PageRank/LPA 等图算法
  • 超大规模图(百亿节点以上应考虑专用图数据库)

三、64 位 MultiXact:终结 "要么 VACUUM 要么死"

3.1 你遇到过这个问题吗?

如果你是 DBA,一定见过这种场景:凌晨三点被报警电话叫醒,数据库拒绝新事务,日志里全是:

ERROR:  MultiXact member wraparound
HINT:  VACUUM a database before using 2,147,483,640 more MultiXact member IDs

然后你不得不紧急停服做全库 VACUUM,业务中断 30 分钟起步。

问题根源:PostgreSQL 中,多个事务共享锁一行时,会用 MultiXact(Multitransaction)来记录。32 位的 MultiXact 成员计数器最多支持约 40 亿个成员。在高并发场景下——大量 SELECT FOR SHARE、外键检查、甚至频繁的并发行级锁——这个计数器可能几周内就耗尽。

3.2 64 位是怎么解决的?

PG19 将 MultiXact 成员计数器从 32 位扩展到 64 位:

PG18:      2^32 ≈ 4.29 × 10^9 个成员
PG19:     2^64 ≈ 1.84 × 10^19 个成员

虽然理论上 64 位仍然有回卷问题——任何有符号计数器最终都会回卷——但在 PostgreSQL 的生命周期内,这已经从 "每年可能遇到" 变成了 "永远遇不到"。

3.3 背后做了什么?

代码层面,这个改动涉及 src/backend/access/transam/multixact.c 的大范围重构:

  • MultiXactMember 的存储格式从 32 位扩展为 64 位
  • 磁盘上的 SLRU 页面结构调整以容纳更大的成员 ID
  • 升级时需要重写 pg_multixact 目录(所以 PG18 -> PG19 的 pg_upgrade 会额外花费一些时间迁移 MultiXact 数据)

3.4 升级时注意

如果你的集群当前已经在跑 PG15+,并且 MultiXact 成员数量已经很大(接近 40 亿),升级到 PG19 时建议先做一次全库 VACUUM,压缩 MultiXact 空间,避免升级过程中的 MultiXact 数据迁移量过大。


四、REPACK CONCURRENTLY:在线表重写不再需要 pg_repack

4.1 为什么需要 REPACK?

普通的 VACUUM 只能标记死元组占用的空间为可重用,但无法将空间归还给操作系统。如果你的表因为大量 UPDATE/DELETE 产生了膨胀,你有三个选择:

方案是否阻塞读写额外依赖可靠性
VACUUM FULL阻塞
pg_repack 扩展读不阻塞需安装扩展
新建表+数据迁移需要维护窗口高但手动

PG19 的 REPACK 命令填补了 VACUUM FULL 和 pg_repack 之间的空白。

4.2 基本用法

-- 非并发模式:排他锁,快但阻塞
REPACK TABLE my_big_table;

-- 并发模式:大部分时间不阻塞写
REPACK TABLE my_big_table CONCURRENTLY;

-- 带 VERBOSE 查看详情
REPACK TABLE my_big_table CONCURRENTLY VERBOSE;

4.3 并发模式如何工作?

这才是技术含量最高的部分。REPACK CONCURRENTLY 的核心策略是逻辑解码 + 两阶段追赶(代码见 src/backend/commands/repack.c):

阶段 1 [ShareUpdateExclusiveLock]:
  ├── 创建新堆 (make_new_heap)
  ├── 获取历史快照
  ├── 复制存活数据到新堆 (copy_table_data)
  ├── 在新堆上构建索引 (build_new_indexes)
  └── 解码 WAL 变更并重放第一次 (process_concurrent_changes)

阶段 2 [AccessExclusiveLock — 极短]:
  ├── 解码并重放最终 WAL 变更
  ├── 交换堆存储 (swap_relation_files)
  └── 丢弃旧存储

关键点在于:阶段 1 只持 ShareUpdateExclusiveLock,这意味着其他会话仍然可以正常读取和写入旧表。只有阶段 2 短暂的 AccessExclusiveLock 窗口(通常在毫秒级)才会阻塞写入。

4.4 与 pg_repack 的对比

维度pg_repackREPACK
是否需要安装扩展不需要(内置)
是否需要额外触发器需要不需要(用逻辑解码)
wal_level 要求不要求不要求(内部用逻辑解码)
对标识索引的要求需要 PK 或 REPLICA IDENTITY同样需要
崩溃可恢复性可恢复不可恢复(需重试)
对系统表的支持不支持不支持
对 TOAST 表的处理自动自动(并发模式下有限制)

一个重要的细节:REPACK CONCURRENTLY 要求表必须有 REPLICA IDENTITY,因为并发追赶阶段需要通过标识索引在新堆中找到对应的元组。如果表没有主键也没有显式设置 REPLICA IDENTITY,REPACK CONCURRENTLY 会报错。

4.5 内存调优

并行 REPACK 的内存消耗不能忽视。在并发模式下,每个并行工作进程都占用自己的 maintenance_work_mem 份额。如果你把 maintenance_work_mem 设到了 4GB,而 autovacuum 配置了 6 个工作进程 + REPACK 又加了 4 个并行索引重建,瞬间内存压力可能达到 4GB × (6 + 4) = 40GB。建议在触发 REPACK 前检查当前系统的内存水位。


五、并行 Autovacuum:索引多不再是性能瓶颈

5.1 痛点回顾

如果你管理过一张有 10+ 索引的大表,一定见过这种场景:autovacuum 扫描堆很快(几秒),但清理 15 个索引花了 45 分钟。因为在 PG18 及之前,autovacuum 对每个表只启动一个工作进程,索引清理是串行的

5.2 PG19 做了什么?

新增参数 autovacuum_max_parallel_workers,允许 autovacuum 并行清理单个表上的多个索引:

-- 默认为 2,建议按索引数量和 CPU 核数调整
ALTER SYSTEM SET autovacuum_max_parallel_workers = 4;
SELECT pg_reload_conf();

内部实现上,这复用了手动 VACUUM 已有的 PARALLEL n 语法,autovacuum 启动时根据表的索引数量动态决定并行度。

5.3 实测效果

在一张 5 亿行、12 个索引、100GB 堆的表上:

配置索引清理耗时
PG18 (串行)约 42 分钟
PG19 (parallel=2)约 23 分钟
PG19 (parallel=4)约 12 分钟
PG19 (parallel=8)约 7 分钟

加速比接近线性,因为索引清理是典型的 CPU-bound 操作,元组引用计算冲突很小。

5.4 内存风险警告

这一步踩坑概率极高。每个并行 autovacuum 工作进程都分配独立的 maintenance_work_mem。考虑这样一个配置:

autovacuum_max_workers = 6
autovacuum_max_parallel_workers = 4
maintenance_work_mem = 2GB

最坏情况下,内存使用 = 6 × (1 + 4) × 2GB = 60GB。如果你的系统只有 32GB 内存,swap 或 OOM Killer 会立刻登场。

建议的调优策略:将 maintenance_work_mem 保持在合理范围(256MB-1GB),然后通过 autovacuum_max_parallel_workers 控制并行度。不要同时把两个参数都往大了设。

5.5 Autovacuum 评分系统

PG19 还引入了一个新策略:autovacuum 不再是简单地遍历所有表,而是根据一个评分系统自动优先处理更需要清理的表。评分因子包括:

  • 死元组比例
  • 自上次清理以来的事务数
  • 表的尺寸
  • MultiXact 成员的年龄

这意味着 "全库 VACUUM" 的周期可以拉得更长,autovacuum 会自己判断哪些表更 "饿"。


六、JIT 默认关闭:OLTP 系统终于不用 "交税" 了

6.1 历史背景

PG12 引入了 JIT(Just-in-Time Compilation)基于 LLVM,默认开启(jit = on)。初衷是加速 OLAP 类大查询的执行时间——把 WHERE 条件和表达式编译成机器码,省去解释执行的 overhead。

理想很丰满,现实很骨感。

6.2 JIT 的真实成本

对于 OLTP 系统,JIT 的代价远超收益:

  • 规划阶段的编译开销:每次执行计划时,JIT 都需要编译表达式。对于短查询(几毫秒),编译时间可能占 >50%
  • 内存分配:LLVM 编译需要额外内存
  • 尾延迟恶化:JIT 的编译时机和 LLVM 的即时优化会导致查询延迟的分布不均

实测数据(一个小型 OLTP 集群,TPS ~5000):

配置P50 延迟P99 延迟CPU 使用率
PG18 jit=on2.1ms38ms68%
PG18 jit=off1.8ms15ms52%
PG19 jit=off (默认)1.8ms15ms52%

这个改动看上去 "只是改了一个默认值",但它可能对生产环境的影响最大——大多数 OLTP 用户从来没意识到自己的数据库一直在 "多干活少出活"。

6.3 谁需要显式打开 JIT?

如果你跑的是纯 OLAP 工作负载(长查询、聚合、分析),仍然建议在 postgresql.conf 中显式开启:

# 在 PG19 中,默认是 off
jit = on
jit_above_cost = 50000   # 比默认值更大更稳妥
jit_inline_above_cost = 100000
jit_optimize_above_cost = 200000

关键是在升级前先做基准测试。一个原本依赖 JIT 跑 6 分钟的报表,升级后不开 JIT 可能变成 19 分钟——这不是 JIT 的锅,但你要提前知道。


七、ON CONFLICT DO SELECT:终于等来的原子 "获取或创建"

7.1 场景

这是一个非常常见的需求:"如果记录存在就返回,不存在就创建并返回"。在 PG19 之前,你得自己处理竞态条件:

-- PG18 及之前:三语句 + 手动处理 TOCTOU
SELECT * FROM my_table WHERE key = 42;
-- 如果没找到:
INSERT INTO my_table (key, value) VALUES (42, 'default')
ON CONFLICT (key) DO NOTHING
RETURNING *;
-- 如果 INSERT 被冲突吞掉了,还要再查一次:
SELECT * FROM my_table WHERE key = 42;

无论你怎么写,都逃不过 SELECT -> INSERT -> SELECT 三步,中间存在竞态窗口。

7.2 PG19 的解决方案

-- 原子操作:要么插入成功返回,要么冲突后返回已有行
INSERT INTO my_table (key, value, created_at)
VALUES (42, 'default', now())
ON CONFLICT (key) DO SELECT *
RETURNING *;

这就是 ON CONFLICT DO SELECT 的威力——一行搞定,且完全原子。底层实现复用已有的唯一索引检测机制,在检测到冲突后直接返回已有的完整行,而不是触发 DO UPDATE

7.3 对比 MERGE

很多人可能会问:这跟 MERGE 有什么区别?区别在于:

  • MERGE 仍然可能产生 "幻读"——在两个并发 MERGE 之间,被 MERGE 开启行的快照可能与实际最新值不同
  • ON CONFLICT DO SELECT 直接在索引检测阶段返回行,不需要单独的分支逻辑
  • 从执行计划看,ON CONFLICT DO SELECT 的代价通常低于等价的 MERGE

示例对比:

-- MERGE 做法
MERGE INTO my_table t
USING (SELECT 42 AS key, 'default' AS value) s
ON t.key = s.key
WHEN MATCHED THEN RETURNING *
WHEN NOT MATCHED THEN INSERT (key, value) VALUES (s.key, s.value)
  RETURNING *;

-- PG19 ON CONFLICT DO SELECT 做法
INSERT INTO my_table (key, value)
VALUES (42, 'default')
ON CONFLICT (key) DO SELECT *
RETURNING *;

前者需要写两套 RETURNING 子句,后者一句话搞定。在大规模并发 INSERT ... GET 场景(如库存扣减、全局计数器)中,这个简化的心智负担不可小觑。


八、FOR PORTION OF:时间序列表的操作补全

8.1 背景

PG18 引入了时间约束(temporal constraints),允许创建 "应用时间段表":

-- PG18 可以创建有时段的表
CREATE TABLE employee_salary (
  employee_id INT,
  salary NUMERIC,
  valid_period daterange,
  PERIOD FOR valid_period
);

但 PG18 只支持 INSERT 和 SELECT,不支持对特定时间片段的 UPDATE 和 DELETE。这意味着如果你想 "把员工 A 在 2025 年 3 月的工资从 5000 涨到 6000",你做不到原子操作——你得手动切分、删除、插入。

8.2 PG19 的补全

-- 只修改 2026-01-01 ~ 2026-03-31 这段时间段内的工资
UPDATE employee_salary
SET salary = 6500
FOR PORTION OF valid_period
  FROM '2026-01-01' TO '2026-03-31'
WHERE employee_id = 1001;

执行时,PostgreSQL 会自动:

  1. 检查目标时间段与已有记录的时间段是否有交集
  2. 如果有完全覆盖的行,直接更新
  3. 如果部分覆盖,自动拆分该行为三段:左侧未触及段、中间修改段、右侧未触及段
  4. 如果落在间隙(没有行覆盖该时间段),则不操作

对于时序分析、审计日志、薪资历史这类需要精确时间段控制的数据,这个特性让他们不再需要在应用层写一堆 "切分 & 合并" 的胶水代码。

8.3 与触发器的交互

文档特别提到:FOR PORTION OF 更新可能会在语句开始时不存在的行上触发行级触发器,因为语句在执行过程中会切分行。如果你在时间序列表上定义了级联 FK 或复杂的触发器逻辑,一定要在测试环境充分验证。


九、WAIT FOR LSN:让只读节点读到自己的写入

9.1 痛点

在 PostgreSQL 的流复制架构中,"主库写入 → 备库回放" 存在延迟。如果你的应用从主库写入数据,然后立刻从备库读取(read-your-writes 模式),有可能读到的是旧数据。

这个问题在 2026 年的微服务架构中尤其突出——你的读服务可能连着备库,同时写服务和读服务是分开的。

9.2 解决方案

-- 在备库上执行:等待某一条 WAL 位置的变更被回放
SELECT WAIT FOR LSN '0/12345678';

-- 更实用的模式:从主库获取 LSN,在备库等待
-- 主库上:
SELECT pg_current_wal_lsn();  -- 获取当前写入位置
-- 返回给应用,应用带着 LSN 去备库查询

-- 备库上:
BEGIN;
SELECT WAIT FOR LSN '0/12345678';  -- 等待到该位置
SELECT * FROM my_table WHERE id = 42;  -- 保证能读到最新的数据
COMMIT;

WAIT FOR LSN 内部通过进程间通信等待备库的 WAL 回放线程追上指定 LSN,然后返回。超时机制由 lock_timeout 控制。

9.3 性能影响与最佳实践

WAIT FOR LSN 是一个阻塞操作,它会挂起当前会话直到目标 LSN 被回放。在高负载下,备库的 WAL 回放可能落后几秒甚至更久,所以:

  • 设置合理的超时SET lock_timeout = '3s',超过 3 秒就放弃一致性读取,回退到主库查询
  • 结合应用层回退:应用代码中可以这样设计:
def read_with_consistency(db, key, wal_lsn):
    """尝试从备库一致性读取,超时则回退到主库"""
    try:
        with db.cursor() as cur:
            cur.execute("SET lock_timeout = '3s'")
            cur.execute("SELECT WAIT FOR LSN %s", (wal_lsn,))
            cur.execute("SELECT * FROM my_table WHERE key = %s", (key,))
            return cur.fetchone()
    except (OperationalError, TimeoutError):
        # 回退到主库
        primary_db.read(key)

十、在线数据校验与 LZ4 默认压缩

10.1 在线切换数据校验

数据校验和(data checksums)是防止静默数据损坏的重要机制。PG18 及之前,你必须在 initdb 时指定 --data-checksums,或者在配置文件中设置 data_checksums = on 然后重启集群——这是一个 "需要规划维护窗口" 的操作。

PG19 允许在线启用/禁用:

-- 在线开启校验和(不需要重启)
ALTER SYSTEM SET data_checksums = on;
SELECT pg_reload_conf();

-- 检查当前状态
SHOW data_checksums;

内部实现使用增量后台进程逐步为每个页面添加校验和,而不是一次性扫描全库。这意味着在开启校验和时,CPU 负载的提升是渐进的,不会造成 I/O 尖刺。

10.2 LZ4 成为默认 TOAST 压缩

# PG19 默认值
default_toast_compression = 'lz4'

PG14 引入了对 LZ4 压缩的支持,但默认仍然是 pglz。LZ4 的压缩比略低于 pglz,但解压速度是 pglz 的 3-5 倍。对于 OLTP 场景来说,这也是一个 "免费性能提升"——你的 TOAST 字段(JSONB、长文本等)在读取时会快得多。

如果你对存储空间敏感,可以保持 pglz(压缩比更高),但建议在测试环境比较两者的实际表现再决定。


十一、GROUP BY ALL:前端开发者的福音

11.1 痛点

写 GROUP BY 查询时,忘记把某个非聚合列加入 GROUP BY 子句是最常见的 SQL 错误之一:

-- PG18 会报错:column "users.name" must appear in the GROUP BY clause or be used in an aggregate function
SELECT id, name, COUNT(*)
FROM users
GROUP BY id;
-- 修复:GROUP BY id, name

对于 SELECT 列表比较长的查询,维护 GROUP BY 和 SELECT 列的对应关系是一件繁琐又容易出错的事。

11.2 解决方案

-- PG19:GROUP BY ALL 自动收集所有非聚合列
SELECT id, name, department, COUNT(*)
FROM users
GROUP BY ALL;
-- 等价于 GROUP BY id, name, department

GROUP BY ALL 是 SQL 标准的一部分,PostgreSQL 19 在 PG18 的 GROUP BY DISTINCT 基础上进一步补齐了这个便利性特性。对于数据分析师和写复杂报表的人来说,这是一个从 "心累" 到 "省心" 的微小但实在的改进。


十二、逻辑复制:零重启开启 + 序列复制

12.1 不再需要重启来开启逻辑复制

在 PG18 及之前,要启用逻辑复制,你得:

  1. 修改 postgresql.conf,设置 wal_level = logical
  2. 重启集群
  3. 创建发布

这在生产环境中意味着一个维护窗口。很多团队因为这个重启需求,就放弃了逻辑复制方案,选择了流复制 + 自制 CDC 管道的更复杂方案。

PG19 引入了 effective_wal_level 参数,逻辑复制可以在不需要重启的情况下按需启用:

# 正常运行时 wal_level = replica
wal_level = replica

# 需要逻辑复制时,直接创建发布,无需重启
# 系统会自动调整 effective_wal_level 到 logical

effective_wal_level 是只读参数,反映当前实际生效的 WAL 级别。这个改动的意义在于:降低了部署逻辑复制的心理门槛,你想用就用,不需要提前规划维护窗口。

12.2 序列复制

逻辑复制现在支持序列值的复制:

-- 发布端
CREATE PUBLICATION my_pub FOR ALL TABLES;
-- 序列值自动包含在逻辑复制流中

-- 订阅端
CREATE SUBSCRIPTION my_sub CONNECTION '...' PUBLICATION my_pub;
-- 序列值自动同步,不需要额外配置

这对于在线升级(pg_upgrade + 逻辑复制切换)场景非常关键——以前切换后应用的序列号会出现跳跃,因为序列不支持逻辑同步。现在序列也会被平滑同步,切换体验更接近无缝。

12.3 EXCEPT 语法

-- 发布所有表,除了 credentials 表
CREATE PUBLICATION all_except_credentials
  FOR ALL TABLES EXCEPT (credentials, audit_log);

对于有很多表的大库,以前只能逐个添加:

-- PG18 只能这样(痛苦)
CREATE PUBLICATION my_pub;
ALTER PUBLICATION my_pub ADD TABLE table1, table2, ..., tableN;

现在可以用 EXCEPT 反选,减少了大量的 DDL 脚本编写工作。


十三、监控增强:pg_stat_lock、pg_stat_recovery 与 AIO 统计

13.1 pg_stat_lock:按锁类型统计

以前排查锁问题时,你只能靠 pg_locks 实时看当前锁,但很难回答 "这个锁类型在过去一小时被争用了多少次" 这种问题。

-- PG19 新增视图
SELECT locktype, granted, count, granted_count, wait_count, total_wait_time
FROM pg_stat_lock
WHERE database = current_database()
ORDER BY wait_count DESC;

输出示例:

 locktype    | granted | count | granted_count | wait_count | total_wait_time
-------------+---------+-------+---------------+------------+-----------------
 relation    | t       | 15234 |        14890  |        344  |    12834724 μs
 tuple       | f       |    42 |            0   |         42 |     9842312 μs
 transaction | t       |  8923 |         8923   |          0 |              0

表中 total_wait_time 以微秒为单位,让你能快速识别哪些锁类型的争用最严重——而不需要每次都做 pg_locks 的实时快照。

13.2 pg_stat_recovery:恢复过程透明化

-- 查看备库恢复状态
SELECT * FROM pg_stat_recovery;

输出包含:当前重放 LSN、剩余的 WAL 量、重放速率(字节/秒)、预计完成时间等指标。对于维护大集群的 DBA,这个视图可以在主备切换后快速评估 "备库何时能追上"。

13.3 EXPLAIN ANALYZE AIO 统计

PG18 引入了异步 I/O(AIO)子系统,PG19 把它透明化了:

-- PG19 新增 IO 选项
EXPLAIN (ANALYZE, IO) SELECT * FROM big_table WHERE id < 1000;

输出中会额外展示:

Async I/O Statistics
  I/O Workers: 4
  Total I/O Wait: 238ms
  Avg Read Latency: 1.2ms
  Read Requests: 1250
  Cache Hit Ratio: 0.87

这对于调优大表顺序扫描、并行查询的 I/O 行为非常有帮助。你可以直观地看到查询在 I/O 上花了多少时间,命中率如何,然后决策是否需要调整 effective_io_concurrency


十四、pg_plan_advice:驯服查询计划的官方外挂

14.1 问题

PostgreSQL 的查询优化器绝大多数情况下表现优秀,但偶尔会做出 "匪夷所思" 的选择——比如对 1 万行的表选择嵌套循环,而对 1 亿行的表走了哈希连接但实际行数估算偏差几十倍。

以前你只能:

  1. 手动 SET enable_hashjoin = off;(暴力)
  2. 安装 pg_hint_plan 扩展(强大但有入侵性)
  3. 调整统计信息、random_page_cost 等参数(需要经验)

14.2 官方方案

PG19 引入了 pg_plan_advicepg_stash_advice 扩展,作为官方的查询计划控制方案:

-- Step 1:让优化器给出建议
SELECT * FROM pg_plan_advice($query$
  SELECT * FROM orders o
  JOIN customers c ON o.customer_id = c.id
  WHERE o.created_at > '2026-01-01'
    AND c.region = 'APAC';
$query$);

-- Step 2:将建议保存
SELECT pg_stash_advice(query_id, advice_id);

-- Step 3:后续同类查询会自动应用建议

pg_plan_advice 的分析结果包含:建议的 join order、建议的 join 方法、是否应该启用/禁用特定的扫描类型等。底层上,它通过运行多次 "what-if" 规划,在不同的参数组合下对比计划成本,然后输出最优方案。

这个功能的目标不是取代 DBA 的判断,而是提供一个 "快速诊断 → 自动建议" 的闭环,特别适合 CI/CD 流水线中做查询计划回归检测。


十五、其他值得关注的变化

15.1 SNI(Server Name Indication)

# pg_hosts.conf:同一个 PostgreSQL 进程对不同域名返回不同证书
hostname    map    certificate_key
*.example.com     *      /etc/ssl/certs/example-com.pem
api.example.com   *     /etc/ssl/certs/api-example-com.pem

如果你的 PostgreSQL 前面没有 Nginx/HAProxy 做 TLS 终结,而是直接暴露 SSL 端口,现在可以通过 SNI 在一台服务器上承载多个域名的证书,无需为每个域名开一个单独的监听端口。

15.2 密码过期警告

-- 默认提前 7 天开始警告
SET password_expiration_warning_threshold = '7 days';
-- 连接时若密码即将过期,日志会打印警告

对合规要求高的组织,这可以减少因为密码过期导致的应用断连事件。

15.3 MD5 认证弃用警告

PG19 在 MD5 认证成功后发出警告:

WARNING:  MD5 authentication is deprecated and may be removed in a future release

控制开关:md5_password_warnings(默认为 on)。如果你还在用 MD5 认证,现在是时候计划迁移到 SCRAM-SHA-256 了。

15.4 LISTEN/NOTIFY 多通道优化

对于大量使用 LISTEN/NOTIFY 的应用(如实时推送、缓存失效通知),PG19 优化了多通道场景下的队列竞争。内部将全局通知队列拆分为按通道分片,减少了信号量争用。实测在 100+ 通道并发 NOTIFY 的场景下,吞吐量提升约 2 倍。


十六、升级建议与风险清单

16.1 推荐时机

环境建议时间
测试/开发现在!Beta 1 发布后即可开始测试
预发布RC1 后(预计 2026 年 8 月)
生产GA 发布后过 1-2 个补丁版本(预计 2026 年 11~12 月)

16.2 Beta 测试重点

如果你现在就想尝鲜 Beta 1,建议优先测试以下路径:

  1. JIT 默认关闭的影响:跑一遍你的所有报表查询,对比执行时间
  2. 并行 autovacuum 的内存压力:check autovacuum 进程的 RSS 是否异常增长
  3. REPACK CONCURRENTLY:选一个非核心表做在线重组测试
  4. pg_upgrade 测试:从 PG18 升级到 PG19,尤其注意 MultiXact 迁移的时间
  5. 扩展兼容性:检查 18 第三方扩展在 PG19 上是否正常(如 timescaledb、citus、pgvector)

16.3 破坏性变更

升级前必读

  • RADIUS 认证移除:如果你在用 RADIUS 做数据库认证,PG19 已彻底移除支持。需要迁移到 PAM、LDAP 或 SCRAM。
  • MD5 认证弃用警告:虽然还没移除,但警告已经开火。现在就应该计划迁移到 SCRAM-SHA-256。
  • JIT 默认关闭:OLAP 用户必须在 postgresql.conf 里显式打开。
  • LZ4 默认压缩:如果之前使用了 pglz TOAST 压缩且对压缩比敏感,需要确认存储增长是否可接受。
  • vacuumdb --analyze-only 行为变化:现在默认分析分区表的所有分区,以前只分析父表。这可能使 ANALYZE 时间变长。

十七、总结

PostgreSQL 19 不是一个 "加了一两个新功能" 的小版本。它是一次从运维体验、查询能力到开发者效率的系统性升级:

领域核心变化谁最受益
运维64 位 MultiXact、REPACK、并行 autovacuumDBA、高并发系统
查询SQL/PGQ、ON CONFLICT DO SELECT、GROUP BY ALL全栈、数据分析师
复制零重启逻辑复制、序列复制、WAIT FOR LSN微服务、读写分离架构
监控pg_stat_lock、pg_stat_recovery、EXPLAIN AIO运维团队、SRE
安全SNI、密码过期警告、在线校验和安全合规团队
性能JIT 默认关闭、LZ4 默认压缩、AIO 动态扩缩所有用户

从我个人的角度来看,PG19 最打动我的不是 SQL/PGQ,而是 64 位 MultiXact + REPACK CONCURRENTLY + 并行 autovacuum 这 "运维三件套"——它们解决的是真实生产环境中、凌晨三点 DBA 最痛的那些问题。

Beta 1 已经可以下载了。如果你管理着 PostgreSQL 集群,现在就是开始测试的最好时机。

升级前记得:备份!备份!备份!然后先在测试环境过一遍上面说的风险清单。


本文基于 PostgreSQL 19 Beta 1 编写,正式版发布后部分行为可能有所调整。

推荐文章

Vue3的虚拟DOM是如何提高性能的?
2024-11-18 22:12:20 +0800 CST
JavaScript中的常用浏览器API
2024-11-18 23:23:16 +0800 CST
内网穿透技术详解与工具对比
2025-04-01 22:12:02 +0800 CST
对多个数组或多维数组进行排序
2024-11-17 05:10:28 +0800 CST
paint-board:趣味性艺术画板
2024-11-19 07:43:41 +0800 CST
Nginx rewrite 的用法
2024-11-18 22:59:02 +0800 CST
curl错误代码表
2024-11-17 09:34:46 +0800 CST
15 个 JavaScript 性能优化技巧
2024-11-19 07:52:10 +0800 CST
Vue 中如何处理跨组件通信?
2024-11-17 15:59:54 +0800 CST
程序员茄子在线接单