首页 / SQLite 入门教程 / VACUUM 与数据库维护

SQLite 入门教程

VACUUM 与数据库维护

本教程共 50 篇 · 第 44 篇 · 更新于 2026-07-31

sqliteVACUUMauto_vacuum维护空间回收

44. VACUUM 与数据库维护

本节目标:学完本章你能用 VACUUM 给数据库瘦身、理解它何时会改变 rowid,并清楚 auto_vacuum 三种模式的区别与代价。

SQLite 是嵌入式、零配置的数据库,没有独立服务进程,整个数据库就是一个文件。这带来一个很实在的好处:你看得见它的体积。但正因为”数据库即文件”,随着你不断插入、更新、删除数据,这个文件也会悄悄出现”虚胖”和”碎片”——删掉的数据留下的空洞并不会自动还给操作系统。

本章就讲怎么给 SQLite 数据库做”整理”与”维护”,主角就是 VACUUM 命令,以及它的近亲 auto_vacuum 模式。

为什么会”虚胖”和”碎片化”

当你从表里 DELETE 掉一大批数据时,SQLite 并不会立刻把对应的磁盘空间擦除还给文件系统。它只是把这些空间标记为”空闲页”(free pages),留着以后插入新数据时复用。结果是:数据库文件占用的字节数可能远大于”实际有效数据”需要的体积。

更严重的是,频繁的插入、更新、删除会让同一张表或索引的数据散落在文件各处,这就是”碎片化”(fragmentation)。碎片化本身不一定让文件变大,但会让读写时在磁盘上跳来跳去,拖慢顺序读取。

重点提示

删除的数据虽然”腾出了空间”,但文件大小不变,这是 SQLite 的默认行为(除非开了 auto_vacuum)。想要真正把文件缩小、把数据排整齐,就得靠 VACUUM。

VACUUM 做了什么

VACUUM 的本质是:把当前数据库的内容复制到一个临时数据库文件里,重新紧凑地排布,再用这个临时文件覆盖原文件。因为它是一次”整体重建”,所以会:

  1. 回收所有空闲页,让文件体积缩到最小;
  2. 把每个表和索引的数据尽量连续存放,减少碎片;
  3. 顺手把已删除内容的痕迹彻底清掉(这对防止被取证恢复删除数据也有意义)。

在命令行里直接执行即可:

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.idorders.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_vacuumVACUUM 不是一回事,效果也不同:auto_vacuum 只是”删数据时顺手把文件缩短”,它不会像 VACUUM 那样把半满的页压实、把数据重排连续。所以开了 auto_vacuum 依然可能有碎片,只是文件不会无谓膨胀。别以为开了它就永远不需要 VACUUM。

还有个硬性限制:auto_vacuumpage_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 = 平时取完货就顺手把仓库尾部收缩一点,省事但码放未必整齐。

懂了这些,你就能根据数据特点,在”手动大扫除”和”平时顺手收拾”之间做出合适的取舍。下一章我们讲数据库的”保险柜”——备份、恢复与导入导出。