为何未查询表的PostgreSQL匿名块会阻止Vacuum清理死行?
为什么未访问表的长匿名块会阻止VACUUM清理死行?
这本质是PostgreSQL多版本并发控制(MVCC)的快照机制导致的,核心逻辑如下:
1. MVCC的可见性规则
PostgreSQL通过事务快照控制每个事务能看到的数据版本:每个事务启动时会生成一个快照,记录当前数据库的事务状态,决定哪些数据版本对该事务可见。
只要有活跃事务的快照还能看到某个旧数据版本(死行),VACUUM就绝对不能清理这个死行——PostgreSQL必须保证任何活跃事务都能看到它启动时应该看到的数据,哪怕这个事务实际上没访问过目标表。
2. 你的场景中的关键问题
你运行的匿名块是一个持续100秒的长事务:从执行开始到循环结束,它一直处于活跃状态,持有一个创建于所有UPDATE操作之前的快照。
- 当第二个连接执行UPDATE时,每一行都会生成新的数据版本,旧版本就成了死行,但这些旧版本在长事务的快照中是“可见”的。
- VACUUM扫描到这些死行时,发现有活跃事务(匿名块)的快照还能看到它们,所以会判定这些死行“不可移除”。
3. 为什么没访问表也会影响?
PostgreSQL的事务快照是全局生效的,不是绑定到单个表的。只要事务处于活跃状态,它的快照就会影响所有表的VACUUM操作——VACUUM必须确保没有任何活跃事务能看到要清理的旧版本,不管这个事务有没有访问过目标表。
验证方式
你可以在匿名块运行期间执行以下查询,确认长事务的活跃状态:
SELECT pid, query, state, xact_start FROM pg_stat_activity WHERE state = 'active';
会看到匿名块对应的事务处于active状态,且xact_start时间早于你执行UPDATE的时间。
内容的提问来源于stack exchange,提问作者Saya
相关产品推荐
相关产品推荐

