PostgreSQL · 错误码速查
PostgreSQL 表膨胀但 autovacuum 停不下来:先找钉住清理视界的老事务
这不是单独一条错误码,而是维护症状:UPDATE/DELETE 后的旧行版本,在仍可能被某个打开事务看见时,VACUUM / autovacuum 都不能清掉。常见钉住视界的来源有三类:① idle in transaction 或持有旧快照的会话(看 pg_stat_activity 的 backend_xid / backend_xmin);② 过期的复制槽(pg_replication_slots.xmin / catalog_xmin);③ 忘记提交的 prepared 事务(pg_prepared_xacts)。先只读定位,再决定结束事务、调整槽或设置 idle_in_transaction_session_timeout;不要把“直接杀会话”当成默认第一步。
官方原文
Even when no significant locks are held, an open transaction prevents vacuuming away recently-dead tuples that may be visible only to this transaction; so remaining idle for a long time can contribute to table bloat.出自 PostgreSQL 文档对参数 idle_in_transaction_session_timeout 的说明(Client Connection Defaults)。同章 Routine Vacuuming 写明:UPDATE/DELETE 不会立刻删除旧行版本,只有在不再对任何事务可见之后,才由 VACUUM 回收。防 wraparound 的官方恢复步骤也明确要求先处理 prepared 事务、长时间打开的事务,以及过期复制槽。
出处:PostgreSQL 18 文档 19.11:Client Connection Defaults(idle_in_transaction_session_timeout) 最后核对:
| 为什么清不掉 | MVCC:旧行版本在仍可能被打开事务看见时不能删;autovacuum 会跑,但删不掉被钉住的那一批 | 出处 |
|---|---|---|
| 会话侧线索 | pg_stat_activity 里 backend_xid / backend_xmin 非空,尤其 state = idle in transaction,且 xact_start 很久 | 出处 |
| 复制槽 | pg_replication_slots.xmin / catalog_xmin:槽需要保留的事务视界;VACUUM 不能清掉该视界之后删除的行 | 出处 |
| 两阶段提交残留 | pg_prepared_xacts 里长期未 COMMIT PREPARED / ROLLBACK PREPARED 的条目同样挡住清理 | 出处 |
| 可选护栏 | idle_in_transaction_session_timeout:终止在打开事务内空闲过久的会话;默认 0(关闭) | 出处 |
autovacuum 在跑 ≠ 死行能被清掉
PostgreSQL 的 UPDATE/DELETE 不会立刻删掉旧行版本:这是 MVCC 的代价。官方 Routine Vacuuming 写明,只有当某个旧版本不再对任何事务可见时,VACUUM 才能回收它占用的空间。所以你看到 autovacuum worker 整天在跑、pg_stat_user_tables.n_dead_tup 却下不来,优先怀疑“清理视界被钉住”,而不是先调大 autovacuum_vacuum_scale_factor。
| 你看到的现象 | 更可能的原因 | 先查什么 |
|---|---|---|
| 某几张热表 n_dead_tup 长期居高,autovacuum 计数在涨 | 有会话持有旧 xmin / 打开事务 | pg_stat_activity 按 xact_start 排序 |
| 整库膨胀,WAL / 磁盘也涨 | 复制槽不消费,或逻辑槽 catalog_xmin 很老 | pg_replication_slots |
| 重启应用后仍清不掉 | prepared 事务或槽还在,与应用连接无关 | pg_prepared_xacts + 槽列表 |
| 只有一台只读备库反馈很慢时主库膨胀 | hot_standby_feedback 把备库 xmin 传回主库 | 备库长事务 + 主库 pg_stat_replication.backend_xmin |
本页讲的是“有东西钉住视界”。若只是单表死元组偏多、没有老事务,见 VACUUM 与死元组只读卡。若重点是 idle in transaction 会话本身的连接占用,见 idle in transaction 只读卡。
2026-11-06 测试库验证(box 本机 PostgreSQL 17.11,自造 10 万行表,关闭该表 autovacuum):另一会话开 REPEATABLE READ 事务读过表后挂着,本会话删除 5 万行再 VACUUM (VERBOSE),输出 tuples: 0 removed, 100000 remain, 50000 are dead but not yet removable,removable cutoff: 771,pg_stat_activity 里那个会话 backend_xmin = 771;老事务结束后再 VACUUM,输出 tuples: 50000 removed。
只读:用 pg_stat_activity 找钉住 xmin 的会话
backend_xid 是该后端的顶层事务号;backend_xmin 是它当前快照的 xmin 视界。任一非空,都可能拖住全局清理。idle in transaction 尤其危险:事务已打开、客户端却迟迟不发下一条语句,锁和死行都会一直留着。
SELECT pid,
usename,
application_name,
datname,
state,
now() - xact_start AS xact_age,
now() - state_change AS idle_age,
backend_xid,
backend_xmin,
left(query, 120) AS query_preview
FROM pg_stat_activity
WHERE backend_xid IS NOT NULL
OR backend_xmin IS NOT NULL
ORDER BY xact_start NULLS LAST;- 先看
xact_age最长的几行:是业务批处理、报表会话,还是连接池里漏了 COMMIT/ROLLBACK 的会话。 state = idle in transaction且idle_age很大:优先让应用侧提交/回滚,或修好连接池的事务边界;不要一上来对所有 pid 执行pg_terminate_backend。- 持有打开游标、或长时间跑在
REPEATABLE READ/SERIALIZABLE下的只读事务,也会通过backend_xmin钉住视界,即使没有写操作。 - 确认业务可中断后,才由具备权限的人结束该会话;杀错会话会中断进行中的写入。
只读:复制槽与 prepared 事务同样能钉住清理
官方在处理 transaction ID wraparound 风险时,给出的恢复顺序就是:先解决旧 prepared 事务 → 结束长时间打开的事务 → 处理过期复制槽 → 再 VACUUM。日常“清不掉死行”也应按同一顺序排除,而不是只盯着 autovacuum 参数。
-- 复制槽:xmin / catalog_xmin 越老,主库能清掉的死行越少
SELECT slot_name,
slot_type,
database,
active,
xmin,
catalog_xmin,
restart_lsn,
confirmed_flush_lsn,
wal_status,
inactive_since
FROM pg_replication_slots
ORDER BY slot_name;
-- 两阶段提交残留
SELECT transaction,
gid,
prepared,
owner,
database,
now() - prepared AS prepared_age
FROM pg_prepared_xacts
ORDER BY prepared;active = false且xmin/catalog_xmin很老:常见于已下线的逻辑复制、迁移工具或忘记删的物理槽;删错仍在用的槽可能迫使备库重建。wal_status为unreserved/lost时,槽本身可能已不可用,但仍要单独评估,不要和“表膨胀”问题混成一步误操作。pg_prepared_xacts非空且prepared_age很长:联系当初发起PREPARE TRANSACTION的事务管理器,用COMMIT PREPARED或ROLLBACK PREPARED(需要相应权限),不要只杀普通后端。
复制槽还会拖住 WAL 删除,表现为 pg_wal 目录膨胀。槽与 WAL 保留的对照见 WAL 保留与复制槽。
可选护栏:idle_in_transaction_session_timeout(不要当成默认杀会话)
参数 idle_in_transaction_session_timeout:会话在打开事务内空闲超过阈值后会被终止。官方说明它正是为了避免空闲事务长时间挡住死行清理。默认是 0(关闭)。这是护栏,不是排障时的第一刀。
-- 先看当前值(只读)
SHOW idle_in_transaction_session_timeout;
-- 推荐:只给容易漏提交的应用角色设置,而不是一刀切改全局
-- 需要具备对该角色的 ALTER 权限(通常是超级用户或该角色的 owner)
-- ALTER ROLE app SET idle_in_transaction_session_timeout = '5min';
-- 会话级临时试验(断开后失效):
-- SET idle_in_transaction_session_timeout = '5min';- 风险:连接池 / 中间件若不能妥善处理服务端突然断开,可能把错误暴露给业务,或把坏连接放回池。
- 范围:不要把超时设到超级用户或 DBA 交互会话上;长事务批处理角色应单独评估,必要时用更高阈值或保持关闭。
- 权限:
ALTER ROLE ... SET需要相应角色管理权限;改postgresql.conf会影响所有会话,官方也不建议把statement_timeout/lock_timeout一类超时无差别写进全局配置。 - 仍不是默认建议:排障当下应先定位具体 pid / 槽 / prepared 条目;超时只用于防止同类问题反复出现。
常见问题
为什么 autovacuum 一直在跑,表还是越来越大?
autovacuum 只能清掉已经对所有事务不可见的死行。若有 idle in transaction、旧快照、复制槽或 prepared 事务钉住 xmin,worker 会反复扫描,却删不掉那批行,表就会继续膨胀。
怎么用 SQL 找出卡住清理的老事务?
查 pg_stat_activity,筛选 backend_xid 或 backend_xmin 非空,按 xact_start 排序。重点看 state 为 idle in transaction 且空闲很久的会话,再对照业务决定提交、回滚还是结束会话。
复制槽也会导致表膨胀吗?
会。pg_replication_slots 的 xmin / catalog_xmin 表示槽仍需要保留的事务视界,VACUUM 不能清掉该视界之后删除的行;过期槽还会额外拖住 WAL。
idle_in_transaction_session_timeout 该设多少?
没有通用最优值。官方默认关闭(0)。可先给容易漏提交的应用角色设几分钟级超时做护栏,并确认连接池能处理服务端断连;批处理或 DBA 会话不要盲目套用。
可以直接 pg_terminate_backend 吗?
不应作为默认第一步。先确认该 pid 对应的业务、事务内容和可中断性;杀错会中断写入。复制槽和 prepared 事务也不是 terminate 普通后端能解决的,要按各自流程处理。
免费 · 留邮箱领检查单
PostgreSQL 14 停更升级检查单(Excel 可直接打开)
- PG14 2026-11-12 停更,升 17 / 18 前后 19 项检查:只读预检、--check、回滚、补统计、回归
- 每项写清怎么查、通过标准、不处理会怎样(附测试库实测数字)
- 配合站上的只读预检 SQL 使用,每个业务库各跑一次
下一步
价格以对应页面当前标价为准。
出处(最后核对 2026-11-06)
- PostgreSQL 18 文档 19.11:Client Connection Defaults(idle_in_transaction_session_timeout)
- PostgreSQL 18 文档 24.1:Routine Vacuuming(Vacuuming Basics;防 wraparound 恢复步骤含 prepared / 长事务 / 复制槽)
- PostgreSQL 18 文档 27.2.3:pg_stat_activity(state、backend_xid、backend_xmin)
- PostgreSQL 18 文档 53.20:pg_replication_slots(xmin、catalog_xmin 与 VACUUM)
- PostgreSQL 18 文档 53.17:pg_prepared_xacts
本页摘录并解读上面列出的官方文档,不是官方页面;官方文档更新后以官方为准。本页内容都来自官方文档,没有我们自己的测试结果。