一组实际运行的 SQLite 实验展示了删除、补写与 VACUUM 后的文件大小和空闲页变化。文章解释 page_count、freelist_count 与 WAL 的关系,并提供可复现 SQL。
一个日志表删掉了九成记录,查询行数已经下降,数据库文件却几乎一样大。对使用 SQLite 保存本地日志、缓存或离线数据的应用来说,行数与文件大小之间的关系,取决于数据库页如何回收。
SQLite 在 2026 年 10 月发布的 3.54.0 中,为命令行加入了 .diskused 磁盘分析命令。空间分析也可以先从三个长期支持的指标入手,观察总页数、空闲页数和每页大小。下面用一组实际运行的数据解释它们的关系。 SQLite 3.54.0 发布记录
删除之后,空闲页增加了
本次实验使用 Windows 上 Node.js v24.19.0 的内置 SQLite 3.53.3,页大小设为 4096 字节,关闭自动收缩,并使用 DELETE 日志模式。建表后插入 10,000 条记录,每条记录的 BLOB 字段固定占 3000 字节,然后依次删除、补写,再执行 VACUUM。
每一步都在事务提交之后读取主数据库文件的字节数。结果如下。
| 操作完成后 | 记录数 | 文件字节数 | 总页数 | 空闲页数 |
|---|---|---|---|---|
| 插入 10,000 条 | 10,000 | 41,058,304 | 10,024 | 0 |
| 删除前 9,000 条 | 1,000 | 41,058,304 | 10,024 | 9,019 |
| 再插入 9,000 条 | 10,000 | 41,062,400 | 10,025 | 0 |
| 删除刚补写的 9,000 条 | 1,000 | 41,062,400 | 10,025 | 9,020 |
| 执行 VACUUM | 1,000 | 4,112,384 | 1,004 | 0 |
第一次删除让记录数降到了原来的十分之一,文件大小保持在 41,058,304 字节,同时出现了 9,019 个空闲页。再次插入同等数量的数据后,文件仅增加了一个 4096 字节的页,空闲页归零。这组结果显示,数据库已经把大量原有空间用于后续写入。
SQLite 在 auto_vacuum=NONE 模式下,会把删除数据释放出的整页空间保留在文件内部,供后续分配复用。文件仍然占着磁盘空间,但其中部分页已经可再次使用。 VACUUM 通过重建数据库,重新组织有效内容并回收多余空间。 SQLite VACUUM 文档
用三个数区分“文件大”和“空闲多”
在数据库连接中运行以下查询即可取得对应指标。
PRAGMA page_size;
PRAGMA page_count;
PRAGMA freelist_count;
page_count 是数据库总页数,freelist_count 是空闲页数。二者乘以 page_size,可以分别估算数据库页的总字节数与空闲整页的字节数。 SQLite PRAGMA 文档
第一次删除后的计算是 9019 × 4096 = 36,941,824 字节。约 35.23 MiB 的整页空间已进入空闲页列表,约占数据库总页数的 90%。
还需把部分填充的数据页考虑在内。一个页可能仍然保存着有效记录,即使内部有剩余空间,也不能把整页算进空闲列表。因此,空闲页比例适合观察可复用的整页空间,完整评估碎片与表、索引的占用则需要进一步的磁盘分析。
本次保留的记录、表结构和索引结构共同占用了剩余页面。数据行数变化与文件大小变化因而具有不同的粒度。
在新建数据库中复现
以下 SQL 用于新建的空实验库,生成相同数量和负载大小的数据。先确认运行库版本与模式。
SELECT sqlite_version();
PRAGMA page_size=4096;
PRAGMA auto_vacuum=NONE;
PRAGMA journal_mode=DELETE;
CREATE TABLE events (
id INTEGER PRIMARY KEY,
payload BLOB NOT NULL
);
BEGIN;
WITH RECURSIVE seq(n) AS (
SELECT 1
UNION ALL
SELECT n + 1 FROM seq WHERE n < 10000
)
INSERT INTO events(payload)
SELECT zeroblob(3000) FROM seq;
COMMIT;
DELETE FROM events WHERE id <= 9000;
SELECT count(*) FROM events;
PRAGMA page_count;
PRAGMA freelist_count;
此时可以记录文件大小,再单独执行下面的操作并重复查询。
VACUUM;
SELECT count(*) FROM events;
PRAGMA page_count;
PRAGMA freelist_count;
具体页数可能随 SQLite 版本和数据布局改变。本次实验的结果只对应上述负载和配置,核心观察项是删除前后的空闲页变化,以及重建之后的文件大小。
对持续滚动写入的日志库,保留下来的空闲页可以承接下一批记录。对完成大量清理后将长期保持较小数据量的库,重建操作则可能明显减少磁盘占用。执行时需要预留工作空间并安排维护时段,官方文档指出普通 VACUUM 可能需要额外达到原数据库大小两倍的可用磁盘空间,且执行连接必须结束已有事务。 VACUUM 执行条件
开启 WAL 后,还要把日志文件算进去
上述实验刻意使用 DELETE 日志模式,使记录的文件大小对应主数据库。WAL 模式会把新修改先追加到 -wal 文件,再通过 checkpoint 将内容写回主数据库。因此,排查应用的实际磁盘占用时,需要同时观察主文件与 WAL 文件,以及 checkpoint 的进展。 SQLite WAL 文档
这也对应两项不同的操作。checkpoint 推进 WAL 中的修改落入主库,VACUUM 重建数据库并压缩布局。对一个文件持续变大的实例,先确认增长来自主库还是 WAL,再检查空闲页,才能确定需要处理的是空间布局还是日志积累。
资料与实验记录日期为 2026 年 11 月 5 日。本文表格来自本地 SQLite 实验,未进行业务吞吐量测试。
参考资料

评论
0 条讨论