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 为例:

  1. 固定页 (Page) 结构 -- 默认 4 KiB。
  2. DELETE / DROP TABLE 时,记录被打上 unused flag ,所在页被挂到 freelist
  3. 文件尾部不会立即截断,因此操作系统看到的文件大小不变。
  4. WAL 模式 下,.db-wal 日志同样采用追加写,旧数据也留在磁盘。
  5. 只有执行 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 会生成 同大小临时文件,磁盘需有足够空间并加排他锁,务必在维护时间执行!

bash 复制代码
# 进入 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#):

csharp 复制代码
using (var db = new LiteDatabase("test.db"))
{
    db.Rebuild(); // 等价于 VACUUM
}

压缩后,你会发现文件大小立刻降到 "活跃数据 + 索引" 所需空间。


4 线上环境的最佳实践

  1. 启用 WAL

    sql 复制代码
    PRAGMA journal_mode=WAL;

    并定时

    sql 复制代码
    PRAGMA wal_checkpoint(TRUNCATE);
  2. 开启 incremental_vacuum + 定期触发

    sql 复制代码
    PRAGMA auto_vacuum=INCREMENTAL;
    PRAGMA incremental_vacuum;      -- 可以指定页数

    这样每晚只回收 N 页,避免一次性 I/O 峰值。

  3. 写扩散 vs. 瘦身平衡

    • 全量 VACUUM:文件最小、查询碎片最少,但重写成本高。
    • auto_vacuum=INCREMENTAL:每日轻量回收,I/O 峰值低。
      结合业务 QPS、磁盘 IOPS 选择折中方案。
  4. 监控 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 大文件吓到。

相关推荐
echoyu.5 分钟前
java源代码、字节码、jvm、jit、aot的关系
java·开发语言·jvm·八股
m0_748248021 小时前
Redis 简介与安装指南
数据库·redis·缓存
Elastic 中国社区官方博客6 小时前
在 Elasticsearch 中使用 Mistral Chat completions 进行上下文工程
大数据·数据库·人工智能·elasticsearch·搜索引擎·ai·全文检索
编程爱好者熊浪8 小时前
两次连接池泄露的BUG
java·数据库
TDengine (老段)9 小时前
TDengine 字符串函数 CHAR 用户手册
java·大数据·数据库·物联网·时序数据库·tdengine·涛思数据
qq7422349849 小时前
Python操作数据库之pyodbc
开发语言·数据库·python
姚远Oracle ACE10 小时前
Oracle 如何计算 AWR 报告中的 Sessions 数量
数据库·oracle
Dxy123931021610 小时前
MySQL的SUBSTRING函数详解与应用
数据库·mysql
码力引擎10 小时前
【零基础学MySQL】第十二章:DCL详解
数据库·mysql·1024程序员节
杨云龙UP11 小时前
【MySQL迁移】MySQL数据库迁移实战(利用mysqldump从Windows 5.7迁至Linux 8.0)
linux·运维·数据库·mysql·mssql