编程 PostgreSQL 19 把 BufferPin 改名成 Buffer:等待事件告警不报错,只静默返回 0 行

2026-11-11 20:01:16

PostgreSQL 19 把 BufferPin 改名成 Buffer:等待事件告警不报错,只静默返回 0 行

PostgreSQL 19 计划在 2026-11-19 GA,RC1 已在 2026-10-15 发布,RC2 定在 2026-11-12。我用应用现有的测试矩阵跑了一遍 RC1,覆盖 PostgreSQL 13 到 19 共 1,208 个场景,只有两个需要改写 SQL,而这两处都由服务端的一处改动引起。

对别人的监控面板来说,最危险的一条恰恰不报错。

参考资料:

一个不报错的失效:BufferPin → Buffer

PostgreSQL 19 把 buffer pin 等待从 wait_event_type = 'BufferPin' 挪到了 Buffer,事件本身改名为 BufferCleanup。旧的过滤条件照样能执行,只是返回 0 行。

用 Docker 起一个实例就能复现:

docker run -d --rm --name pg19 -e POSTGRES_PASSWORD=x postgres:19rc1
docker exec -it pg19 psql -U postgres

会话 A 用打开的游标持住一个 pin:

CREATE TABLE b AS SELECT g AS id FROM generate_series(1, 100) g;
BEGIN;
DECLARE c CURSOR FOR SELECT * FROM b;
FETCH 1 FROM c;

会话 B 等在它后面:

VACUUM (FREEZE) b;

会话 C 先看真实等待,再用旧式告警的写法计数:

SELECT wait_event_type, wait_event, state, left(query, 24) AS query
FROM pg_stat_activity WHERE query LIKE 'VACUUM%';

SELECT count(*) AS waiting_on_pins
FROM pg_stat_activity WHERE wait_event_type = 'BufferPin';

在 PostgreSQL 18.6 上:

 wait_event_type | wait_event | state  |        query
-----------------+------------+--------+--------------------
 BufferPin       | BufferPin  | active | VACUUM (FREEZE) b;
 waiting_on_pins
-----------------
               1

在 PostgreSQL 19 RC 1 上:

 wait_event_type |  wait_event   | state  |        query
-----------------+---------------+--------+--------------------
 Buffer          | BufferCleanup | active | VACUUM (FREEZE) b;
 waiting_on_pins
-----------------
               0

告警不会报错、不会缺列,只是静默返回 0 行。这就是它比后面那四处 ERROR 更隐蔽的原因——后者第一次跑就会炸出来。

把过滤条件直接改成 Buffer 也不对:19 的等待事件表里,BufferExclusive、BufferShared、BufferShareExclusive 都挂在这个 type 下,它们等的是 buffer 锁,语义和 pin 不是一回事。同时兼容两个版本的写法是:

WHERE wait_event_type = 'BufferPin' OR wait_event = 'BufferCleanup'

会直接报错的四处

这些在第一次运行时就抛出错误:

ERROR: column "sync_error_count" does not exist
ERROR: unrecognized configuration parameter "io_workers"
ERROR: unrecognized configuration parameter "escape_string_warning"
ERROR: non-standard string literals are not supported

18 → 19 RC 1 的对应关系:

  • pg_stat_subscription_stats.sync_error_count → sync_seq_error_count 和 sync_table_error_count
  • io_workers → io_min_workers(Docker 镜像里为 2)和 io_max_workers(8)
  • escape_string_warning → 移除
  • SET standard_conforming_strings = off → 被拒,SQLSTATE 0A000

在自己的仓库里一次性找全:

grep -rnE "BufferPin|sync_error_count|escape_string_warning|io_workers|standard_conforming_strings" .

登录后多出的两条 WARNING

PostgreSQL 19 可以在认证之后、第一条查询之前向客户端发 WARNING:

WARNING: authenticated with an MD5-encrypted password
DETAIL: MD5 password support is deprecated and will be removed in a future release of PostgreSQL.

WARNING: role password will expire soon
DETAIL: The password for role "expuser" will expire in 1 day.

过期提醒的窗口由 password_expiration_warning_threshold 控制,RC1 上默认值是 7d。

复现方式:在 pg_hba.conf 最上方给测试角色加一行 md5 或 scram-sha-256,然后走 TCP 连接。Docker 镜像默认对本地连接用 trust,而 trust 会跳过这两条警告,所以在容器里直接 psql 是看不到的。

值得自查的一点:如果驱动封装层或健康检查把登录阶段收到的任何服务端消息都当作失败,这两条 WARNING 会直接表现为连不上。

新增的系统视图

  • pg_stat_autovacuum_scores:当前库里每张表一行,给出 autovacuum 排序所用的分数。
  • pg_stat_lock:每种锁类型一行,集群级锁统计。
  • pg_stat_recovery:最多一行,startup 进程的恢复状态。
  • pg_stat_progress_repack:每个正在跑 REPACK、CLUSTER 或 VACUUM FULL 的后端一行。pg_stat_progress_cluster 为向后兼容保留。

新增列,以及 SELECT * 的位置解码

我的 drift 脚本会读取应用依赖的列、设置、函数和扩展版本,在每个大版本上各跑一次。13 到 19 共七个版本上有 180 行完全一致,18 与 19 之间有 7 行不同:

  • pg_replication_slots.slotsync_skip_reason 新增列
  • pg_stat_all_indexes.stats_reset 新增列
  • pg_stat_all_tables.stats_reset 新增列
  • pg_stat_user_indexes.stats_reset 新增列
  • pg_stat_user_tables.stats_reset 新增列
  • pg_stat_statements 1.12 → 1.13,两行,每个版本一行

显式写出列名的查询不受新增列影响。会踩坑的是 SELECT * 配合按位置解码的代码。另外 pg_stat_statements 1.13 加了 generic_plan_calls 和 custom_plan_calls,按扩展版本分支的逻辑需要补一个 1.13 的分支,或者改成「1.13 及以上」的规则。

其他会破坏应用的变更

  • JIT 默认关闭:依赖 JIT 的大型分析查询可能变慢。把最重的几张报表用 19 跑一遍,有回退就按角色或按查询设 jit = on。
  • max_locks_per_transaction 默认 64 → 128:显式设置过这个值的实例,等于容量被砍半,尤其是分区表多的时候,自定义值要翻倍。
  • standard_conforming_strings 恒为 on:用旧版 pg_dump 且关掉该设置导出的 dump 无法导入,需要用 PostgreSQL 19 的 pg_dump 重新导出。
  • json_array() 空子查询返回 [] 而不是 NULL:判断 IS NULL 的代码会漏掉空结果。
  • MD5 登录告警:md5_password_warnings 可以关掉这条警告。
  • btree_gist 的 inet/cidr 索引:pg_upgrade 会直接拒绝执行,需要先用 GiST opclass 重建这些索引。
  • RADIUS 支持和 MULE_INTERNAL 编码移除。
  • SQL/PGQ 属性图查询和 GROUP BY ALL 不在 19 里:前者在 9 月被回退,后者也没进 RC1,CREATE PROPERTY GRAPH 和 GROUP BY ALL 在 RC1 上都报语法错误。

升级前检查清单

  1. 现在就把测试套件跑在 RC1 上,PGDG apt 源里已有 RC1 包。
  2. 逐个确认扩展。每个扩展都需要针对 19 的构建:pg_hint_plan 1.9.0(10 月 18 日发布)加了 PostgreSQL 19 兼容;pgvector 需要有支持 19 的版本。
  3. 在生产副本上跑 pg_upgrade --check,btree_gist 的 inet/cidr 索引和改名问题都会在这一步暴露。
  4. 用 PostgreSQL 19 的 pg_dump 重新导出,不要拿旧 dump 直接喂给 19。
  5. 在 JIT 关闭的状态下,把面板背后的查询重新做一次基准测试。
  6. 更新监控:修掉读了改名列的查询,补上 pg_stat_autovacuum_scores 和 pg_stat_lock。
  7. 驱动和 ORM 的集成测试也要跑,不能只测 SQL。

RC1 上还实测过几个开发特性:窗口函数的 IGNORE NULLS(lag(users) IGNORE NULLS OVER w 可以拿每次读数跟上一个真实值比)、INSERT ... ON CONFLICT DO SELECT ... RETURNING(返回冲突行,可用 FOR UPDATE/FOR SHARE 加锁)、COPY TO FORMAT json 配 FORCE_ARRAY、COPY FROM ON_ERROR SET_NULL,以及用 REPACK (CONCURRENTLY) 取代 VACUUM FULL/CLUSTER。运维侧的改动包括:autovacuum_max_parallel_workers 支持索引并行 autovacuum、新的打分机制、备库上的 WAIT FOR LSN 读己之写、wal_level=replica 时逻辑复制自动启用、序列值复制、default_toast_compression 默认改为 lz4、EXPLAIN (ANALYZE, IO) 报告异步 I/O,以及 io worker 池在 io_min_workers 和 io_max_workers 之间伸缩。

复制全文 生成海报 PostgreSQL 数据库 运维 监控 升级

推荐文章

程序员茄子在线接单