PostgreSQL中VACUUM FULL无法收缩膨胀表体积问题问询
PostgreSQL 6行数据占143MB、VACUUM/VACUUM FULL无法收缩问题根因与解决
核心根因
VACUUM输出明确提示25564 dead row versions cannot be removed yet, oldest xmin: 87657915,说明全局存在一个未推进的最老事务ID(xmin),所有晚于该ID生成的死元组对这个老快照仍然可见,无论是普通VACUUM还是VACUUM FULL都不能违反MVCC可见性规则删除这些元组,因此无法收缩空间。
此前排查的长事务、废弃复制槽、孤立预编译事务三类常见阻塞源不全,漏了两类高频场景:
- 业务侧会话持有未释放的快照:比如未关闭的游标(尤其是WITH HOLD游标)、长时间不提交的空闲事务、驱动层自动开启的未释放事务,哪怕会话不执行任何操作,只要持有快照就会卡住xmin推进。
- Azure PaaS平台侧的系统进程持有xmin:Azure PostgreSQL Flexible Server的内置高可用流复制、PITR自动备份的WAL归档进程使用系统级复制槽,存储在
azure_sys系统库中,普通用户在业务库查询pg_replication_slots无法看到这类槽位,一旦备库复制延迟过高、备份归档卡住,就会持续卡住主库xmin不推进。
注意:VACUUM FULL需要持有ACCESS EXCLUSIVE锁重写表,但重写过程仍然要遵守全局MVCC规则,只要存在老xmin,就必须把对老快照可见的死元组全部复制到新表中,因此无法实现收缩效果。
排查步骤
直接通过报错提示的xmin定位阻塞源,执行以下SQL:
SELECT pid, backend_type, xmin, xact_start, query_start, state, query FROM pg_stat_activity WHERE xmin = 87657915 OR backend_xmin = 87657915;
- 如果返回业务侧会话:查看会话状态,若为空闲事务、打开未关闭的游标,直接执行
SELECT pg_terminate_backend(返回的pid);终止会话即可释放xmin。 - 如果未返回任何结果:说明是Azure平台侧进程持有xmin,去Azure控制台查看PostgreSQL实例的高可用复制延迟、备份任务状态,等待复制延迟追平、备份归档完成后,xmin会自动推进。
立即修复方案
- 等xmin推进后(可重复执行
VACUUM VERBOSE bloated_table,看到提示存在可移除元组即可),执行VACUUM FULL bloated_table;就能正常收缩空间。 - 如果不想等待平台侧状态恢复,可在业务低峰期短暂停写该表,通过重建表绕过MVCC限制,操作耗时极短:
-- 1. 创建结构、索引、约束与原表完全一致的新表 CREATE TABLE bloated_table_new (LIKE bloated_table INCLUDING ALL); -- 2. 导入当前可见的6行活数据 INSERT INTO bloated_table_new SELECT * FROM bloated_table; -- 3. 原子替换原表 ALTER TABLE bloated_table RENAME TO bloated_table_old; ALTER TABLE bloated_table_new RENAME TO bloated_table; -- 4. 确认业务访问正常、外键约束生效后删除旧表 DROP TABLE bloated_table_old;
长期优化建议
- 该表仅6行数据且高频更新,将表的fillfactor设置为50,预留足够的页面空间给HOT更新(当前HOT更新比例约98%,预留空间后可以进一步减少死元组跨页产生的空间占用):
ALTER TABLE bloated_table SET (fillfactor = 50); - 业务侧排查连接池配置,确认事务提交后及时释放连接快照,避免存在未关闭的游标、长空闲事务持有老xmin。
- 配置监控告警,关注实例复制延迟、备份任务状态,避免平台侧任务卡住导致xmin长期不推进。
内容的提问来源于stack exchange,提问作者Giorgio Morina
相关产品推荐
相关产品推荐

