有人清理完SQLite数据库里的历史数据后,发现磁盘上的数据库文件一点没变小,甚至执行了整表删除后文件反而增大。这其实是SQLite重用空间的正常表现。数据库在删除记录时,并不会立刻把空间释放给操作系统,而是将这些页放入空闲链表,供后续插入、更新时复用。想让文件真正瘦身,需要根据使用场景选择合适的整理或重建手段。

需要把数据库文件真正压缩下来,就要先看懂页分配和空闲页机制。SQLite把数据保存在固定大小的页中,页大小通常在1KB到64KB之间,默认由编译参数或建库时指定。无论是删除一行还是整张表,SQLite只会修改B-tree中的指针和页头信息,把相关页标记为空闲,而不会移动文件末尾的页,因此文件占用的物理大小不会下降。频繁的UPDATE还会在B-tree内部留下半满页和碎片页,这些页可能只被少量使用,却会拖慢查询。
一、先摸清数据库的膨胀程度
动手压缩前,建议先查看几个关键参数,判断当前文件里到底有多少空间属于空闲页。PRAGMA freelist_count 返回空闲页数量,PRAGMA page_count 返回总页数,两者相减就能估算出有效数据页的占比。比如总页数10万,空闲页2万,说明约有20%的空间没有被有效利用。结合页大小,可以进一步算出可回收的字节数。
PRAGMA page_count; PRAGMA freelist_count; PRAGMA page_size;
如果数据库启用了WAL模式,还需要观察wal文件本身的大小。PRAGMA journal_mode 可以查看当前日志模式,PRAGMA wal_checkpoint 用来触发检查点。WAL文件长期不回收时,也可能比主数据库还大。先定位膨胀来源,后续操作才更有针对性,而不是盲目执行VACUUM。
二、VACUUM与auto_vacuum:两种主流压缩思路
VACUUM 命令会重新扫描整个数据库,将所有有效数据复制到一个临时的数据库文件,然后替换原文件。这个过程能彻底清除空闲页、整理B-tree碎片,并把文件末尾的空白空间截断,达到物理缩小的效果。它的缺点也很明显:执行期间需要额外的磁盘空间,写入操作会被阻塞,库很大时耗时可能很长。因此通常建议在应用维护窗口或启动阶段执行。
VACUUM;
如果只是想观察执行VACUUM后文件能缩小多少,可以先记录 PRAGMA freelist_count 的结果,或者先备份再测试。需要注意,VACUUM不能在事务中执行,如果有未提交事务或活跃的读连接,可能失败并返回锁库错误。对于持续运行的应用,最好先关闭多余连接,或者通过 PRAGMA busy_timeout 设置合理的等待时间。
另一种思路是开启 auto_vacuum,让数据库在删除数据后自动逐步回收空闲页。创建数据库时指定 PRAGMA auto_vacuum = FULL 或 INCREMENTAL 后,SQLite会在内部维护空闲页的回收信息。不过 auto_vacuum 会带来少量写入放大,写入性能会有轻微下降,适合频繁删除、不方便定期执行VACUUM的应用。
PRAGMA auto_vacuum = FULL; VACUUM;
如果数据库已经存在,需要先执行一次VACUUM才会改变auto_vacuum设置。FULL模式在提交时尽可能回收,INCREMENTAL模式则可配合 PRAGMA incremental_vacuum(N) 手动控制回收页数,避免一次性占用过多资源。两种模式各有取舍,需要根据实际写入频率决定是否启用。
三、WAL模式下的压缩顺序
如果数据库开启了WAL日志,直接执行VACUUM可能无法缩小wal文件。VACUUM只处理主数据库文件,不会清空WAL日志。正确做法是先做checkpoint,把WAL中的已提交数据合并回主数据库,再根据情况截断WAL文件。
PRAGMA wal_checkpoint(TRUNCATE);
TRUNCATE 参数会在checkpoint后把WAL文件截断到零字节,前提是没有其他连接正在读WAL。如果存在长连接的读事务,checkpoint可能无法完成,只能使用 PASSIVE 或 FULL 模式尝试推进。对于应用中的长连接,可在空闲时段关闭读连接,或切换数据库为 journal_mode=DELETE,让日志回到传统回滚日志模式,再做压缩。
PRAGMA journal_mode = DELETE; VACUUM; PRAGMA journal_mode = WAL;
这样做的好处是压缩完可以继续使用WAL模式,适合需要高并发读写的场景。但切换journal_mode需要短暂的独占锁,线上环境要放在低峰期执行。如果只想截断WAL文件而不进行主库压缩,也可以单独使用 wal_checkpoint(TRUNCATE),它对主库事务的阻塞时间更短。
四、彻底重建数据库文件
如果VACUUM后效果仍不理想,或者希望完全重置数据库文件结构,可以通过逻辑导出再导入的方式重建。SQLite自带的 .dump 命令会把表结构、索引、数据都导出为SQL语句,导入新文件时所有页会按最新顺序重新分配,效果通常比VACUUM更彻底。这种方式还能顺带清理长期运行后产生的索引碎片。
sqlite3 old.db ".dump" | sqlite3 new.db
对于在应用程序中执行,可以使用Python的 sqlite3 标准库提供备份接口。下面代码把一个数据库完整复制到另一个文件,生成的新库会去掉大部分内部碎片,且不需要手动处理导出导入过程。
import sqlite3
src = sqlite3.connect("old.db")
dst = sqlite3.connect("new.db")
with dst:
src.backup(dst)
src.close()
dst.close()重建完成后,需要校验新库的完整性,并重新附加或替换原文件。可以使用 PRAGMA integrity_check 确认索引和数据没有损坏。对于大库,建议先备份原文件,再使用文件重命名替换,避免复制过程中断导致数据丢失。压缩完成后,也可以执行一次 ANALYZE,让查询优化器基于新库的数据分布重新生成统计信息。
SQLite数据库压缩数据库瘦身VACUUM命令修改时间:2026-09-25 08:58:43