【SQLite 内核】单文件格式与页面头

💡 原文中文,约10100字,阅读约需24分钟。
📝

内容提要

本文介绍SQLite单文件格式:前100字节为数据库头,记录页大小、版本、计数器等自描述字段;B-Tree页头用1字节区分4种页类型,cell指针数组与内容区相向增长;页1固定为schema根页。通过hexdump实测验证字段,并澄清页大小不可随意更改、页1非用户表等常见误解。

🔎

延伸解读

页大小为何不能随意修改

页大小是文件级常量,所有页的物理偏移量都由它决定,因此不支持混合页大小。只有空库才能直接设置,已有数据必须用 VACUUM 重建整个文件。此外,65536 字节的页大小用特殊值 1 表示,这是格式中的编码陷阱,容易误读。

page 1 的真实身份

page 1 恒为 sqlite_schema 表的根页,并非用户表。用户表的根页号记录在 schema 行中,通常从 2 开始,但并非固定。实测中,表 t 的根页为 2,通过 EXPLAIN 的 OpenRead 指令可验证。

cell 布局的动态增长机制

cell pointer array 与 content area 相向增长,插入新 cell 时只需在数组尾部追加指针、在内容区顶部写入数据,无需移动已有 cell。这种设计保证了插入的高效性,但中间空闲区会逐渐缩小,理解这一点有助于后续理解 B-Tree 分裂与页填充。

Q&A

SQLite数据库文件的页大小有什么限制?为什么65536字节的页大小用特殊值1表示?

SQLite数据库的页大小必须是2的整数次幂,范围从512到65536字节。由于页大小字段只有2字节,最大只能表示65535,因此规范规定当该字段值为1时,代表页大小为65536字节。

SQLite数据库文件的前100字节是什么?包含哪些关键字段?

前100字节是数据库头(database header),包含魔数("SQLite format 3\0")、页大小、读写版本、预留空间、文件变更计数器、数据库页数、freelist信息、schema cookie、文本编码、用户版本、应用ID等自描述字段。

SQLite的B-Tree页面头中,页类型标志有哪些?分别代表什么?

页类型标志有4种:0x0d(13)表示叶子表B-Tree,0x05(5)表示内部表B-Tree,0x0a(10)表示叶子索引B-Tree,0x02(2)表示内部索引B-Tree。内部页额外包含一个4字节的右子页指针。

SQLite中cell pointer array和content area是如何布局的?为什么插入操作不需要移动已有数据?

cell pointer array从页头之后向页尾方向增长,而cell内容区从页尾向页头方向增长,两者相向增长。插入新cell时,只需在pointer array尾部追加一个指针,并在content area顶部写入新内容,无需移动已有数据,逻辑顺序由pointer array维护。

为什么SQLite的page 1固定是sqlite_schema表的根页?用户表的根页号如何确定?

page 1固定是sqlite_schema表的根页,这是硬编码规则。用户表和索引的根页号记录在sqlite_schema的行中,通常第一个建的表落在page 2,但这不是规范保证的固定值,会随空间分配变化。

SQLite的file change counter和schema cookie有什么区别?

file change counter记录写事务提交的次数,任何INSERT/UPDATE/COMMIT都会使其加1;schema cookie只在DDL(如CREATE TABLE、DROP INDEX)后加1。两者是独立的计数器。

SQLite的页大小可以随意更改吗?如何更改?

页大小不能随意更改。只有在数据库为空(尚无行数据)时,设置PRAGMA page_size才会立即生效;已有数据的库要改页大小,必须配合VACUUM重建整个文件,因为文件格式不支持混合页大小。

SQLite的overflow页是什么?它的作用是什么?

当一条记录太大装不进单个页时,多出的部分会溢出到overflow页链。每个overflow页开头4字节是下一个overflow页的页号(0表示链尾)。overflow阈值的计算保证叶子页至少能容纳4个cell,避免B-Tree退化。

🏷️

标签

➡️

继续阅读