VACUUM
1. 背景:为什么需要 VACUUM?
在 SQLite 的日常使用中,你会发现一个奇怪的现象:当你从数据库中删除了大量数据后,数据库文件的大小(在磁盘占用的空间)并没有变小。
这是因为 SQLite 的默认行为:
- 当你执行
DELETE删除数据时,SQLite 只是将这些数据占用的页面标记为“空闲”(Free Pages)。 - 这些空闲页面保留在数据库文件中,等待将来插入新数据时重复使用。
- 它不会主动将这部分空间归还给操作系统。
这种机制可以提高性能(避免频繁的文件截断和扩展操作),但会导致数据库文件包含大量的“气泡”(碎片),导致磁盘空间浪费,且数据分布分散可能影响查询速度。
VACUUM 命令就是用来解决这个问题的。
2. VACUUM 的作用
VACUUM 命令主要执行两个操作:
- 重建数据库文件:它会创建一个全新的数据库文件,将原有数据库中的有效数据紧凑地复制过去,丢弃所有的空闲页面。
- 最小化文件体积:操作完成后,新的数据库文件只包含实际数据,体积会变小,释放出的磁盘空间归还给操作系统。
可以将它类比为电脑硬盘的“磁盘碎片整理”。
3. 基本用法
语法非常简单,通常不需要任何参数:
你也可以对附加(Attached)的数据库执行此操作:
4. 执行过程与内部原理
理解 VACUUM 的内部工作原理对于评估其风险至关重要:
- 创建临时文件 :SQLite 在同一目录下创建一个临时的数据库文件。
- 复制数据 :从源数据库读取有效数据,重新整理并按顺序写入临时文件。此时会忽略所有的空闲页面。
- 替换文件 :复制完成后,SQLite 删除源文件,并将临时文件重命名为源文件名。
- 重新加载 :重新打开新的数据库文件。
5. 重要注意事项与风险
在生产环境中使用 VACUUM 前,必须了解以下限制:
5.1 磁盘空间峰值
由于 VACUUM 会创建一个完整的副本,在执行过程中,你需要至少拥有相当于原数据库文件大小两倍的剩余磁盘空间。
- 如果你的数据库是 10GB,且磁盘剩余空间只有 5GB,执行
VACUUM会失败。
5.2 独占锁(Exclusive Lock)
VACUUM 操作需要独占访问权。
- 在执行期间,数据库被完全锁定。
- 任何其他的读取或写入操作都无法进行,直到
VACUUM完成。 - 对于大型数据库,这可能意味着几秒甚至几分钟的停机时间。
5.3 ROWID 的变化
如果你的表中没有显式定义 INTEGER PRIMARY KEY,表使用的是系统生成的隐式 ROWID。
VACUUM 重建表时,可能会重置这些隐式的 ROWID。
- 建议:总是显式定义主键,以避免此问题。
6. VACUUM INTO (备份功能)
从 SQLite 3.27.0 (2019年) 开始,引入了 VACUUM INTO 语法。
这是一个生成数据库备份的绝佳方式。与直接复制文件不同,VACUUM INTO 是事务安全的,可以在数据库被应用程序使用的同时执行。
生成的 backup.db 是一个经过压缩整理的、最小化的数据库副本,非常适合作为备份文件传输。
7. 自动清理 (Auto Vacuum)
除了手动执行 VACUUM,SQLite 还提供了 auto_vacuum 模式,通过 PRAGMA 设置。
查看当前模式:
0或NONE(默认):不自动回收空间。1或FULL:自动回收。2或INCREMENTAL:增量回收。
为什么通常不推荐 FULL 模式?
虽然 PRAGMA auto_vacuum = FULL 可以让 SQLite 在每次事务提交后自动裁剪文件大小,但这会造成严重的碎片化和性能损耗。因为它必须频繁地移动页面来填补空白,导致写入速度变慢。
推荐策略
对于大多数应用:
- 保持默认设置 (
auto_vacuum = NONE)。 - 在维护窗口期(例如每天深夜或每周一次)手动执行
VACUUM。 - 或者仅在检测到空闲页面比例过高时执行(通过
PRAGMA freelist_count查询)。
8. 总结
- 用途:回收删除数据后的磁盘空间,整理碎片,减小文件体积。
- 代价:需要双倍磁盘空间,且会长时间锁定数据库。
- 最佳实践:不要频繁运行。通常在大量删除操作(如清理旧日志)之后,或在定期维护任务中运行。
- 高级技巧:使用
VACUUM INTO进行安全、紧凑的备份。

