关注「索引目录」公众号,获取更多干货。
本文探讨SQLite 的安全网,包括freelists、leaf pages、trunk pages和 journals 。
为什么 SQLite 需要空闲列表
SQLite 从不立即将未使用的页面返回给操作系统。
一旦页面被分配给数据库文件,除非进行显式的收缩操作,否则它将一直保留在该文件中。
当行被删除、索引被删除或表被移除时,页面将变为非活动状态。
SQLite 不会丢弃这些页面,而是将它们放入一个名为freelist 的结构中。
freelist 只是 SQLite 内部未使用的页面清单,这些页面随时可以重新用于未来的插入操作,而不会增加文件大小。
什么是自由列表?
空闲列表是一个直接嵌入到数据库文件中的链接结构。
关键事实:
-
第一个空闲列表主干页码存储在文件头偏移量为32 的位置。 - 剩余页数总数
存储在偏移量 36 处。 -
所有免费页面均被跟踪,无垃圾回收,无歧义。
SQLite 将空闲列表组织成一个有根的树状列表,从文件头开始向外分支。
树干页和叶子页(自由列表页)
自由列表页面分为两种子类型:
主干页
主干页面是一个免费页面的目录。
其布局(从页面开头开始):
- 4 字节→下一主干页
的页码( 0如果没有页码则为 0) - 4 字节
→ 此主干上存储的叶子指针的数量。 - N × 4 字节
→ 叶子页的页码
每个主干页面可以同时引用多个免费页面。
叶子页
叶子页面是指没有任何实际结构的自由页面。其内容未指定,可能包含先前使用留下的垃圾信息。
叶子页面才是真正可重用的页面。主干页面只是指向它们。
页面如何进入和退出自由列表
当页面变为非活动状态时,SQLite会将其添加到空闲列表中。但页面实际上仍然保留在数据库文件中。
当需要写入新数据时:
这就解释了为什么数据库经常增长但不会自动缩小。
缩小数据库:VACUUM 和 Autovacuum
如果空闲列表过大,就会造成磁盘浪费。SQLite 提供了两种解决方案:
真空
这是一项难度很高但又非常精准的操作。
自动吸尘模式
自动清理功能以运行时开销为代价来换取空间清理。
SQLite 中的日志文件
日志文件是一种崩溃恢复文件,用于记录数据库更改,以便 SQLite 可以回滚未完成的事务。
它保证了原子性和持久性,确保数据库在发生故障后永远不会只写入一半。
SQLite 历来使用传统日志记录方式,其中包括:
- 回滚日志
- 声明日志
- 主日志
从 SQLite 3.7.0 开始,数据库要么使用传统日志记录,要么使用 WAL,绝不会同时使用两者。
内存数据库完全跳过日志记录,所有操作都在内存中完成。
回滚日志:SQLite 的安全带
每个数据库都有一个回滚日志文件:
-
存储在与数据库相同的目录中 -journal通过在数据库文件名后附加名称来命名 -
在写入事务开始时创建 -
交易完成后(默认情况下)删除
回滚日志存储数据库页面之前的图像,允许 SQLite 在出现问题时恢复数据库。
回滚日志结构
回滚日志被分为多个日志段。
每个部分包含:
- 段头
- 一条或多条日志记录
大多数情况下,只有一个片段。多个片段只会在特殊情况下出现。
章节标题:第一道防线
每个段头都以八个魔数开头:
D9 D5 05 F9 20 A1 63 D7
这些字节仅用于完整性检查。
头部还存储着:
- 日志记录数 (nRec)
-
用于校验和计算的随机值 - 原始数据库页数
- 磁盘扇区大小
- 数据库页面大小
头部始终占用一个磁盘扇区,所有值均以大端格式存储。
期刊保留模式
默认情况下,SQLite会在提交或回滚后删除日志文件。
您可以使用以下方法更改此设置:
DELETE(默认) PERSISTTRUNCATE
在独占锁定模式下,日志文件会在事务之间保留,但其头部会在使用之间失效或被截断。
异步事务(不安全但速度快)
SQLite支持异步模式:
-
日志文件和数据库文件永远不会被刷新。 -
更快捷的交易 nRec设置为 -1-
恢复过程依赖于文件大小,而不是元数据
此模式不具备防崩溃功能,主要用于开发或测试场景,但确实可以提高性能。
为什么这一层很重要
深入到这一层面,SQLite 的理念才得以展现:
-
空间是可以循环利用的,而不是被丢弃的。 -
通过精确、最少的元数据实现安全性 -
一切皆有明确规定,所有信息都会被追踪记录。 -
恢复逻辑直接编码到文件结构中
关注「索引目录」公众号,获取更多干货。

