VACUUM 与数据库维护
本教程共 50 篇 · 第 44 篇 · 更新于 2026-07-31
44. VACUUM 与数据库维护
本节目标:学完本章你能用 VACUUM 给数据库瘦身、理解它何时会改变 rowid,并清楚 auto_vacuum 三种模式的区别与代价。
SQLite 是嵌入式、零配置的数据库,没有独立服务进程,整个数据库就是一个文件。这带来一个很实在的好处:你看得见它的体积。但正因为”数据库即文件”,随着你不断插入、更新、删除数据,这个文件也会悄悄出现”虚胖”和”碎片”——删掉的数据留下的空洞并不会自动还给操作系统。
本章就讲怎么给 SQLite 数据库做”整理”与”维护”,主角就是 VACUUM 命令,以及它的近亲 auto_vacuum 模式。
为什么会”虚胖”和”碎片化”
当你从表里 DELETE 掉一大批数据时,SQLite 并不会立刻把对应的磁盘空间擦除还给文件系统。它只是把这些空间标记为”空闲页”(free pages),留着以后插入新数据时复用。结果是:数据库文件占用的字节数可能远大于”实际有效数据”需要的体积。
更严重的是,频繁的插入、更新、删除会让同一张表或索引的数据散落在文件各处,这就是”碎片化”(fragmentation)。碎片化本身不一定让文件变大,但会让读写时在磁盘上跳来跳去,拖慢顺序读取。
重点提示
删除的数据虽然”腾出了空间”,但文件大小不变,这是 SQLite 的默认行为(除非开了 auto_vacuum)。想要真正把文件缩小、把数据排整齐,就得靠 VACUUM。
VACUUM 做了什么
VACUUM 的本质是:把当前数据库的内容复制到一个临时数据库文件里,重新紧凑地排布,再用这个临时文件覆盖原文件。因为它是一次”整体重建”,所以会:
- 回收所有空闲页,让文件体积缩到最小;
- 把每个表和索引的数据尽量连续存放,减少碎片;
- 顺手把已删除内容的痕迹彻底清掉(这对防止被取证恢复删除数据也有意义)。
在命令行里直接执行即可:
sqlite> VACUUM;
也可以只整理某个附加数据库(schema 名);注意 VACUUM 只能作用于整个数据库或某个附加库的 schema,不能只整理单张表:
sqlite> VACUUM; -- 整理整个主数据库(等价于 VACUUM main;)
sqlite> VACUUM main; -- 显式指定 schema 名
常见坑
VACUUM 执行期间,需要约等于”原数据库两倍大小”的空闲磁盘空间(一份临时文件 + 一份原文件)。在磁盘紧张的小设备(比如嵌入式、边缘设备)上跑 VACUUM,务必先确认空间够。另外,VACUUM 是一个写操作,如果当时有其他连接持有写锁、或本连接有未结束的事务/未 finalize 的语句,它会失败。
VACUUM 会改变 rowid 吗
还记得第 12 章讲的:普通表都有一个隐式的 rowid,而声明了 INTEGER PRIMARY KEY 的列就是 rowid 的别名,自带隐式自增(不需要 AUTOINCREMENT 关键字)。
关键点来了:VACUUM 可能会改变那些”没有显式 INTEGER PRIMARY KEY”的表的 rowid。因为重建时行的物理顺序可能变化,rowid 会被重新编号。但如果你用的是 INTEGER PRIMARY KEY(如我们的 users.id、orders.id),rowid 就是你的主键,VACUUM 不会去改它——你的主键稳如泰山。
实用技巧
这也是为什么第 12 章强调:想要稳定、自带自增的主键,就用
INTEGER PRIMARY KEY,而不是另搞一套。VACUUM 之后主键不变,业务逻辑才不会因为”主键被重排”而出错。
VACUUM INTO:边备份边整理
SQLite 3.15.0 起支持 VACUUM INTO,它不清空原库,而是把”整理后的副本”写到一个新文件:
sqlite> VACUUM INTO 'clean_copy.db';
这个文件的参数必须是一个还不存在(或为空)的文件,否则报错。它的好处是:产出的副本体积最小、且不含任何已删除内容的残留痕迹,相当于”备份 + 瘦身”一步到位。相比官方备份 API,它更省文件系统 I/O,但不支持增量拷贝。
auto_vacuum:自动回收的三种模式
与其事后手动 VACUUM,能不能让 SQLite 自己维持苗条?答案是 auto_vacuum(自动收缩),它有三个档位,通过 PRAGMA 设置:
PRAGMA auto_vacuum = NONE; -- 0:关闭(默认),删除后留空闲页,文件不自动缩小
PRAGMA auto_vacuum = FULL; -- 1:删除数据时把空闲页移到文件末尾并缩小文件
PRAGMA auto_vacuum = INCREMENTAL;-- 2:增量模式,空闲页不自动归还 OS,需另行触发
- NONE(默认):最省 CPU,但文件会虚胖,得靠手动 VACUUM 收尾。
- FULL:删除即收缩文件,但会带来额外的碎片,且写入时开销更大。
- INCREMENTAL:折中,删除后空闲页只进入空闲链表、文件不会自动缩小,需要时用
PRAGMA incremental_vacuum逐步把空闲页归还操作系统、回收空间,产生的碎片比 FULL 少。
常见坑
auto_vacuum和VACUUM不是一回事,效果也不同:auto_vacuum 只是”删数据时顺手把文件缩短”,它不会像 VACUUM 那样把半满的页压实、把数据重排连续。所以开了 auto_vacuum 依然可能有碎片,只是文件不会无谓膨胀。别以为开了它就永远不需要 VACUUM。
还有个硬性限制:auto_vacuum 和 page_size 必须在数据库文件创建之前就定好(除非不在 WAL 模式下、用 PRAGMA 改完立刻 VACUUM)。所以对新建库,如果想用 auto_vacuum,最好在第一次建库时就设好:
sqlite> PRAGMA auto_vacuum = INCREMENTAL;
sqlite> VACUUM; -- 让设置真正生效到文件
日常维护清单
除了 VACUUM,几个常用的”体检”与”保养”命令也值得记牢:
PRAGMA integrity_check;—— 检查数据库有没有损坏,出问题会报具体错误。PRAGMA foreign_keys = ON;—— 若你的表用了外键约束,每次连接都要开(SQLite 默认关闭,第 13 章强调过),维护脚本里也别忘了。ANALYZE;—— 收集表和索引的数据分布统计,帮查询优化器(第 42 章的 EQB)做出更聪明的选择,尤其多索引二选一时。VACUUM;—— 周期性(如夜间)对增长快、删除多的库做一次,回收空间、去碎片。
重点提示
对”写少读多、偶尔大批量删除”的库,定期手动 VACUUM 最划算;对”持续高频写入”的库,考虑 INCREMENTAL 自动收缩,避免每次删除都付出 FULL 的碎片代价。
类比小结
把数据库文件想象成一间仓库:
- 你不停进货(INSERT)、取货(DELETE),腾出的空货架还在原位,但仓库总面积没缩小——这就是”虚胖”;
- 货物被搬得东一件西一件,找起来要满场跑——这就是”碎片”;
VACUUM= 把整间仓库清空、按最优布局重新码放,既腾地方又整齐,但过程需要另租一间临时仓(双倍空间);auto_vacuum= 平时取完货就顺手把仓库尾部收缩一点,省事但码放未必整齐。
懂了这些,你就能根据数据特点,在”手动大扫除”和”平时顺手收拾”之间做出合适的取舍。下一章我们讲数据库的”保险柜”——备份、恢复与导入导出。