PostgreSQL中VACUUM FULL后堆读取增多、普通VACUUM无效的原因
实验步骤
- 创建
bookings表,并禁用Autovacuum - 执行索引仅扫描查询,无heap fetches(堆抓取)
- 更新100条数据后,查询出现295次heap fetches
- 执行
VACUUM FULL后,heap fetches大量增加 - 执行普通
VACUUM后,heap fetches消失
问题
- 为何执行
VACUUM FULL后会出现大量heap fetches? - 为何普通
VACUUM无法立即消除heap fetches,仍存在该现象?
问题解答
1. VACUUM FULL导致heap fetches激增的原因
VACUUM FULL会对表做完全重写:它会把所有有效行(未被标记为过期/删除的行)复制到全新的磁盘空间,随后删除原表文件,再将新文件重命名为原表名。但这个操作不会同步重建索引——索引中存储的仍是旧堆元组的物理地址(CTID),而新表的行存储位置和旧表完全不同,导致索引里的CTID全部失效。
当执行索引扫描时,数据库用索引里的旧CTID去堆中查找,必然找不到有效行,这时会触发大量heap fetch:数据库会沿着行版本链查找最新有效行,或反复回表验证,最终导致heap fetches数量暴增。只有后续执行REINDEX重建索引,索引才会更新为新的CTID,heap fetches才会回落。
2. 普通VACUUM初期无法消除heap fetches的原因
PostgreSQL采用MVCC模型,更新操作不会直接修改原行,而是标记原行为过期,再插入新的行版本。此时索引会同时指向旧的过期行和新的有效行。
普通VACUUM的核心作用是清理堆中的过期元组,更新可见性映射(visibility map),但它不会主动修改索引条目——索引里仍保留着指向旧过期行的CTID。当执行索引扫描时,数据库会先通过索引找到所有匹配的CTID(包括过期行的),再去堆中验证行的可见性:如果发现是过期行,就会顺着版本链查找最新有效行,这个过程就会产生heap fetches。
此外你禁用了Autovacuum,索引的自动懒清理不会触发,需要等待手动VACUUM完成索引的懒清理,或是后续查询操作逐步淘汰过期索引条目,这就是为什么初期普通VACUUM后heap fetches仍存在,之后才消失的原因。
内容的提问来源于stack exchange,提问作者Kirill Kobyshev

