XID 回卷问题处理
XID wraparound 是什么?
PostgreSQL 用 32 位事务号(XID);旧版本文档常用“约 20 亿(2^31)”阈值来描述危险区。为防止旧行“穿越时空”,系统通过 冻结(FREEZE) 把很老的行标记为永远可见,从而允许重用旧 XID。冻结由 VACUUM 完成(含 autovacuum 的“防环绕”模式)。
常见原因
- 大写入负载过饱和
- AutoVacuum 未正确运行
- AutoVacuum 未正确配置(集群/DB/表)
- 长事务/长游标持有很老的快照
- 两阶段提交,长时间未提交
- 复制阻塞,复制进度不更新,从库反馈
- MultiXact 积压
- 系统BUG(如 hypercore)
后面详细介绍每种根因定位与处理的方法。
常用SQL
检查并清理整个数据库
检查数据库的年龄,以及 MultiXact 年龄(PG13+,如果启用 MultiXact)
SELECT datname, age(datfrozenxid), mxid_age(datminmxid) FROM pg_database ORDER BY 2 DESC;以最大速度,执行整库 Vacuum。
SET vacuum_cost_delay = 0; -- 不间断 VACUUM
SET vacuum_cost_limit = 10000; -- 最大化 VACUUM 速度
SET maintenance_work_mem = '1GB'; -- 视实例内存调整
VACUUM (FREEZE, VERBOSE) ; -- 全库(当前 DB)另外也可以使用外部命令 vacuumdb 来处理
# 仅处理“年龄≥X”的最老表,优先清风险
vacuumdb --all --freeze --min-xid-age=50000000 --jobs=4 --verbose
# 如需减少争用,可加 --skip-locked(会跳过拿不到锁的表,谨慎)找到年龄最大的表进行清理
找到年龄最大的数据库,进入其中,找出当前数据库中年龄最大的表:
SELECT n.nspname, c.relname::text AS relname, age(c.relfrozenxid) AS age, mxid_age(c.relminmxid) AS mxid_age,
pg_size_pretty(pg_table_size(c.oid)) AS tbl_size, st.n_dead_tup, st.last_autovacuum, st.last_vacuum
FROM pg_class c JOIN pg_namespace n ON n.oid = c.relnamespace LEFT JOIN pg_stat_all_tables st ON st.relid = c.oid
WHERE c.relkind IN ('r','m') AND n.nspname NOT IN ('pg_catalog','information_schema')
ORDER BY age DESC LIMIT 50;针对单表执行 Vacuum,可以指定并行数量,有多种参数可选择
SET vacuum_cost_delay = 0; -- 不间断 VACUUM
SET vacuum_cost_limit = 10000; -- 最大化 VACUUM 速度
SET maintenance_work_mem = '1GB'; -- 视实例内存调整
VACUUM (FREEZE, VERBOSE, PARALLEL 2, INDEX_CLEANUP OFF) monitor.heartbeat; -- 替换为你自己的表名与此同时可以查阅 Vacuum 的进度。
SELECT * FROM pg_stat_progress_vacuum; \watch 1分析问题
检查AutoVacuum配置是否正常
列出有过表级自定义参数的表,看看是否有表级别的 AutoVacuum 参数覆盖
SELECT n.nspname, c.relname, c.reloptions
FROM pg_class c
JOIN pg_namespace n ON n.oid = c.relnamespace
WHERE c.reloptions IS NOT NULL;检查长事务
查阅 PGSQL XACT 监控面板,或者使用 SQL:
SELECT pid, usename, xact_start, now()-xact_start AS xact_age, state, query
FROM pg_stat_activity WHERE xact_start IS NOT NULL ORDER BY xact_start;检查复制槽:
-- 4) 复制槽是否拖住冻结/清理
SELECT slot_name, slot_type, active, xmin, catalog_xmin, restart_lsn
FROM pg_replication_slots
ORDER BY age(coalesce(catalog_xmin, xmin)) DESC NULLS LAST;SELECT pv.pid, pv.relid::regclass AS relation, pv.phase,
pv.heap_blks_total, pv.heap_blks_scanned, pv.heap_blks_vacuumed,
sa.wait_event_type, sa.wait_event, pg_blocking_pids(pv.pid) AS blockers
FROM pg_stat_progress_vacuum pv
JOIN pg_stat_activity sa USING (pid);最后更新于