SQLite / LiteDB 单文件数据库为何“清空表后仍占几 GB”?——原理解析与空间回收实战
关键词: SQLite、LiteDB、VACUUM、WAL、auto_vacuum、文件瘦身、数据库维护
在嵌入式或桌面、IoT 网关等场景,很多同学都会选择单文件数据库(SQLite、LiteDB、SQL CE…)。
最近群里一位朋友反馈:
“我的
test.db
已经把业务数据全删光,但文件依旧 3.8 GB!
甚至重启程序也没变小,这是为啥?”
别急,这并不是磁盘“被占用不释放”,而是单文件数据库按页管理、延迟回收的正常机制。本文将:
- 揭秘删除数据后文件仍超大的五大原因
- 手把手演示在 Linux / Windows 下 VACUUM / Rebuild 压缩操作
- 给出线上环境自动瘦身与写放大平衡的最佳实践
1 原理:删除 ≠ 收缩,空闲页仍在文件里
以 SQLite 为例:
- 固定页 (Page) 结构 – 默认 4 KiB。
DELETE
/DROP TABLE
时,记录被打上 unused flag,所在页被挂到 freelist。- 文件尾部不会立即截断,因此操作系统看到的文件大小不变。
- 在 WAL 模式 下,
.db-wal
日志同样采用追加写,旧数据也留在磁盘。 - 只有执行
VACUUM
(或auto_vacuum=FULL
时的新页写满)才会真正搬运存活数据 → 重写文件 → 释放未用 page。
LiteDB(.NET 生态)等其他单文件 DB 也采用相似策略,名字不同(如 Rebuild()
)而已。
2 五大常见原因对照表
# | 文件仍巨大的原因 | 是否“异常” | 解决手段 |
---|---|---|---|
1 | 空闲页积累:大量删除后未 VACUUM | ⚠️ 正常 | VACUUM; |
2 | WAL 日志残留:.db-wal + .db-shm 未 checkpoint | ⚠️ 正常 | PRAGMA wal_checkpoint(TRUNCATE); 后 VACUUM; |
3 | 预扩容:曾设置 max_page_count 、或工具一次写满 | ✅ 设计如此 | 取消限制 → VACUUM; |
4 | 隐藏/历史表:程序保留备份、软删除表 | 取决于需求 | 删除对象 → VACUUM; |
5 | 非 SQLite:LiteDB、ESENT、LevelDB 等 | ⚠️ 正常 | 对应的 Shrink /Compact API |
3 手动压缩:三步到位
?? 注意:
VACUUM
会生成 同大小临时文件,磁盘需有足够空间并加排他锁,务必在维护时间执行!
# 进入 sqlite3 shell
sqlite3 test.db-- 1) 看看是否在 WAL 模式
PRAGMA journal_mode;-- 2) 手动触发 checkpoint(若是 WAL)
PRAGMA wal_checkpoint(TRUNCATE);-- 3) 查看空闲页统计
PRAGMA freelist_count;-- 4) 真正压缩
VACUUM;.quit
若使用 LiteDB(C#):
using (var db = new LiteDatabase("test.db"))
{db.Rebuild(); // 等价于 VACUUM
}
压缩后,你会发现文件大小立刻降到 “活跃数据 + 索引” 所需空间。
4 线上环境的最佳实践
-
启用 WAL
PRAGMA journal_mode=WAL;
并定时
PRAGMA wal_checkpoint(TRUNCATE);
-
开启 incremental_vacuum + 定期触发
PRAGMA auto_vacuum=INCREMENTAL; PRAGMA incremental_vacuum; -- 可以指定页数
这样每晚只回收 N 页,避免一次性 I/O 峰值。
-
写扩散 vs. 瘦身平衡
- 全量
VACUUM
:文件最小、查询碎片最少,但重写成本高。 auto_vacuum=INCREMENTAL
:每日轻量回收,I/O 峰值低。
结合业务 QPS、磁盘 IOPS 选择折中方案。
- 全量
-
监控 freelist_count + 文件大小
通过PRAGMA freelist_count;
与 Node 导出指标,当空闲页阈值 > 30 % 时触发压缩任务。
5 FAQ
问题 | 解答 |
---|---|
VACUUM 会锁表吗? | 会对整个 DB 加 排他锁,期间所有读写被阻塞。WAL 模式也不例外。 |
磁盘不足怎么压缩? | 可先复制到大磁盘、《或》用 VACUUM INTO 'new.db'; 再替换。 |
WAL 文件多大合理? | 官方建议 < 1 GiB;通过 PRAGMA wal_checkpoint(TRUNCATE) 清空到 ~0 字节。 |
能否自动释放? | 设置 auto_vacuum=FULL ,但写放大会增 I/O;线上更常用 INCREMENTAL 。 |
结语
“删光数据文件却不变小” 99 % 都是因为你没有 VACUUM / Rebuild。
了解单文件数据库的页式存储后,你就能自信地将文件瘦到最精简,也避免下次被 3 GB 大文件吓到。