VACUUM 和 VACUUM FULL 有什么本质区别?
在 PostgreSQL 等基于 MVCC(多版本并发控制)的数据库中,VACUUM 和 VACUUM FULL 的本质区别可以用一句话概括:
VACUUM 是“清理房间并腾出空位供以后使用”(逻辑清理),而 VACUUM FULL 是“直接盖一栋更小的新楼并搬过去”(物理重写)。
以下是它们的具体本质区别和详细对比:
1. 空间回收的本质(是否将空间还给操作系统)
- VACUUM(普通清理):
- 本质: 扫描表,找到那些被删除或更新后遗留的“死元组”(Dead Tuples),把它们占用的空间标记为“可用”(写入 Free Space Map)。
- 结果: 表的物理文件大小不会变小。腾出来的空间会留在表文件内部,供未来执行
INSERT或UPDATE时重复使用。(注:只有当死元组刚好位于文件末尾时,普通 VACUUM 才可能会截断文件并归还少量空间)。
- VACUUM FULL(完全清理):
- 本质: 彻底重写整个表和它的索引。它会读取表中的“活数据”,然后写入到一个全新的物理文件中,最后删除旧文件。
- 结果: 表的物理文件大小会缩减到最小,省出来的磁盘空间会被真正归还给操作系统。
2. 并发控制的本质(是否阻塞业务)
- VACUUM:
- 非阻塞: 它使用的是
SHARE UPDATE EXCLUSIVE锁。这意味着在清理垃圾的同时,允许其他事务对该表进行正常的读写(SELECT,INSERT,UPDATE,DELETE)。 - 对线上业务影响较小,适合日常自动化维护(AutoVacuum)。
- 非阻塞: 它使用的是
- VACUUM FULL:
- 完全阻塞: 它使用的是
ACCESS EXCLUSIVE锁(排他锁)。在整个执行期间,禁止任何其他事务访问该表(连SELECT读操作都不行)。 - 如果表很大,业务将面临长时间的停机等待。
- 完全阻塞: 它使用的是
3. 系统资源消耗的本质
- VACUUM:
- 不需要额外的磁盘空间。它只是在现有的文件上进行标记。I/O 开销相对较小。
- VACUUM FULL:
- 需要翻倍的磁盘空间: 因为它是把数据写到新文件,所以执行期间,磁盘上会同时存在旧表和新表。如果你的表有 100GB,你的磁盘必须至少还有 100GB 的空闲空间才能执行成功。
- CPU 和 I/O 开销巨大。
形象的比喻:电影院
假设数据库表是一家电影院,数据是观众。当观众看完电影离开(执行 DELETE 或 UPDATE)后:
- 普通 VACUUM: 保洁员进去把空座位上的垃圾清理掉,并在登记本上标记这些座位是“空”的。下一批观众买票时,可以直接安排到这些座位上。(电影院的规模/占地面积没有变,电影照常放映)。
- VACUUM FULL: 把所有还在看电影的观众赶出来,在旁边盖一个座位数刚刚好只够这些观众坐的新电影院,让他们进去接着看,然后把老电影院直接炸掉。(电影院占地面积变小了,但重建期间谁也看不成电影,且需要额外的地皮来盖新电影院)。
核心对比总结表
| 特性 | 普通 VACUUM | VACUUM FULL |
|---|---|---|
| 工作方式 | 标记死元组,记录可用空间 | 重写整张表和索引到新文件 |
| 文件大小 | 通常不变(内部产生碎片空间) | 缩小到极致(消除碎片) |
| 归还空间给OS | 否(留给未来的 Insert/Update 用) | 是 |
| 是否锁表 | 否(允许并发读写) | 是(完全锁死,拒绝读写) |
| 执行速度 | 快 | 极慢(取决于表大小) |
| 额外磁盘要求 | 不需要 | 需要额外一倍的表大小空间 |
| 使用场景 | 日常维护(Autovacuum 自动执行) | 灾难/极度膨胀救援(手动执行) |
实际工作中的建议:
- 千万不要在业务高峰期对大表执行
VACUUM FULL,会导致严重的生产事故(业务卡死)。 - 正常情况下,依赖 PostgreSQL 自带的
autovacuum进程进行普通的VACUUM就足够了。 - 如果因为大批量删除数据导致表严重膨胀(Bloat),急需收回磁盘空间,又不想锁表,建议使用第三方插件
pg_repack或pg_squeeze,它们可以在不阻塞读写的情况下实现类似VACUUM FULL的空间回收效果。