prepared transaction 是脱离会话独立存在的两阶段提交事务。执行
PREPARE TRANSACTION 'some-gid' 后,原会话不再持有事务,但事务占用的锁和事务 ID 会写入 WAL、记录在共享内存中,崩溃恢复后依然存在,直到有人执行 COMMIT PREPARED 或 ROLLBACK PREPARED。max_prepared_transactions 控制服务器同时保留多少个这类事务,默认值为 0,上限 262143,属于 postmaster 级参数,开启和关闭都需要重启。默认值并非一直是 0。8.3 及以前是 5,Tom Lane 在 8.4(2009)改为 0,原因是项目已多次遇到 prepared transaction 被遗忘、最终引发严重维护问题甚至 anti-wraparound 停机,而此前默认非零只是为了回归测试能覆盖该特性。同一提交还把 prepared transaction 写进了 wraparound 警告的 HINT,在 18 上依然保留。
在 18.6 上实测:准备一个更新了一行的事务后断开,新会话里pg_stat_activity看不到任何 backend,但pg_locks中仍有该表的 RowExclusiveLock、索引锁以及自身 XID 的 ExclusiveLock,pid 为空。对该表执行ALTER TABLE ... ADD COLUMN会一直等待直到 lock_timeout 取消;VACUUM (VERBOSE)报告 500 个死元组不可回收,removable cutoff 等于该事务的 XID。它像普通 backend 一样占用 PGPROC 槽位,只是没有进程附着。
很多手段对它无效:idle_in_transaction_session_timeout没有会话可超时;transaction_timeout(17 起)在 PREPARE 时停止计时;pg_terminate_backend()没有对象可终止;以-m immediate重启后日志显示从共享内存恢复 prepared transaction 754;DROP DATABASE和pg_upgrade --check都会拒绝。创建逻辑复制槽也会被它阻塞。唯一出口是COMMIT PREPARED或ROLLBACK PREPARED,需连接同一数据库,且需超级用户或准备该事务的角色。
真正需要这个参数的只有三种场景:XA 事务管理器(如 JTA、Atomikos、Narayana)跨 PostgreSQL 与消息队列或第二个数据库协调提交;Citus 11 起对每个多分片写入在 worker 间做两阶段提交,官方建议在每个 worker 上调高;以及
two_phase = on(15 起)的逻辑复制订阅,订阅端需要准备发布端已准备的事务。除此之外,文档明确表示该命令是为事务管理器准备的,不写事务管理器就不该使用它。若确需开启,建议设为
max_connections,理由是每个会话在管理器卡住时都可能有一个待决事务,而达到上限会在 prepare 阶段转为回滚,正是事务管理器要避免的失败。共享内存开销接近一个连接:18.6 上一千个槽位增加 47 MB,一千个连接增加 51 MB,一百个槽位约 4 MB。备库规则更关键:该参数与 max_connections、max_locks_per_transaction、max_wal_senders、max_worker_processes 同属备库不得小于主库的五个参数,否则备库无法启动或暂停恢复。调整顺序是先备库、后主库。开启前应把 pg_prepared_xacts 中 prepared 超过 5 分钟的记录纳入监控,真正的两阶段提交只停留毫秒级,超时未决的就是孤儿事务,正在占用锁和 vacuum 视野。