SQLite文件格式分析v102Word格式.docx
- 文档编号:21282702
- 上传时间:2023-01-29
- 格式:DOCX
- 页数:38
- 大小:211.37KB
SQLite文件格式分析v102Word格式.docx
《SQLite文件格式分析v102Word格式.docx》由会员分享,可在线阅读,更多相关《SQLite文件格式分析v102Word格式.docx(38页珍藏版)》请在冰豆网上搜索。
(源码网整理:
)
1.2术语
本文涉及的绝大多数术语都是在出现时再进行简单解释,但还是有个别概念需要先说明清楚,比如:
(1)Btree、B-tree和B+tree:
Btree是为磁盘存储而优化了的一种树结构,其一般性说明可参考各类《数据结构》教材。
根据实现方法的不同,Btree又分为很多类型。
在SQLite中,存储表数据用的是B+tree,存储表索引用的是B-tree。
由于历史原因,SQLite在3.0版以前只使用B-tree,从3.0版开始,才对表数据使用了B+tree。
因此,在SQLite的官方文档中,有时B-tree表示存储表索引的B-tree,有时又是两种Btree的统称。
本文中将两种Btree的概念加以了区分,而将Btree作为两种树的统称,这是与SQLite官方文档及当前大多数SQLite介绍性文档相区别的地方。
(2)auto-vacuum数据库:
一般情况下,当一个事务从数据库中删除了数据并提交后,数据库文件的大小保持不变。
即使整页的数据都被删除,该页也会变成“空闲页”等待再次被使用,而不会实际地被从数据库文件中删除。
执行vacuum操作,可以通过重建数据库文件来清除数据库内所有的未用空间,使数据库文件变小。
但是,如果一个数据库在创建时被指定为auto_vacuum数据库,当删除事务提交时,数据库文件会自动缩小。
使用auto_vacuum数据库可以节省空间,但却会增加数据库操作的时间,有利有弊。
Auto_vacuum数据库需要使用附加的格式,如指针图页(本文第6章有介绍),本文重点讨论非auto_vacuum数据库。
(3)数据库映像、数据库文件和日志文件:
“数据库映像”是SQLite数据库的磁盘映像。
SQLite数据库存储在单一的“数据库文件”中。
一般情况下,数据库映像和数据库文件是一致的,可以理解为数据库映像就是数据库文件的内容,但有例外。
如果事务对数据库进行了修改,这些修改会暂存在“日志文件”中,此时可以认为数据库映像分布在数据库文件和日志文件两个文件中。
日志文件有自己的格式,本文第7章专门介绍。
(4)SQLite的当前版本:
我开始写这篇东西时,SQLite的当前版本为3.6.18。
现在已经变成3.6.20了,文件格式没变。
2小文件的分析
2.1准备数据库
执行SQLite的命令行工具,创建一个新的数据库food_test.db。
D:
\SQLite\CLP>
sqlite3foods_test.db
创建一个新表。
CREATETABLEfoods(
idintegerprimarykey,
type_idinteger,
nametext);
插入2条记录。
INSERTINTO"
foods"
VALUES(1,1,'
Bagels'
);
VALUES(2,1,'
Bagels,raisin'
退出命令行工具。
当前目录下多了一个文件foods_test.db,大小为2K。
现在,就可以用UltraEdit或WinHex之类的软件对其进行分析了。
2.2SQLite文件格式
SQLite有3类数据库。
除内存数据库外,SQLite把每个数据库(main或temp)都存储到一个单独的文件中。
SQLite数据库文件由固定大小的“页(page)”组成。
页的大小可以在512~32768之间(包含这两个值,必须是2的指数),默认大小为1024个字节(1KB)。
页大小可以在数据库刚刚创建时设置,一旦创建了数据库对象之后,这个值就不能再改变了。
数据库中所有的页从1开始顺序编号。
在具体的实现中,页号用4字节来表示,并限制最大页号不得超过2^31(参pager.c)。
文件的第1个页被称为page1,第2个页被称为page2,依此类推。
编号为0的页表示“无此页”。
注:
关于一个数据库文件中可以有多少个页,SQLite是这样实现的:
为了限制数据库文件的大小(不要太大),SQLite在程序中对文件页数进行了限制,文件页数的最大值默认为1073741823,在sqliteLimit.h中定义,可以在运行时改变。
页的类型可以是:
Btree页、空闲(free)页或溢出(overflow)页。
Btree又可以是B-tree或B+tree,每一种树的结点又区分为内部页和叶子页。
一个数据库文件中可能没有空闲页或溢出页,但必然有Btree页。
关于Btree页的格式规定是SQLite数据库的核心内容,本文的前半部分都是在介绍Btree页。
其实SQLite还有两种页。
一种称为“锁页(lockingpage)”。
只有1页,位于数据库文件偏移为1G开始的地方,如果文件不足1G,就没有此页。
该页是用于文件加锁的区域,不能存储数据(参源代码io.h)。
好在,如果只是读数据,即使文件大于1G,也不会有指针指向此页,因此下面我们就不再提它了。
另一种称为“指针位图页(pointer-mappage)”,这类页用于在auto-vacuum的数据库中存储元数据。
本文不涉及auto-vacuum数据库,也就不讨论这种页了。
从逻辑上来说,一个SQLite数据库文件由多个多重Btree构成。
每个Btree存储一个表的数据或一个表的索引,索引采用B-tree,而表数据采用B+tree,每个Btree占用至少一个完整的页,每个页是Btree的一个结点。
每个表或索引的第1个页称为根页,所有表或索引的根页编号都存储在系统表sqlite_master中,表sqlite_master的根页为page1。
sqlite_master是一个系统表,保存了数据库的schema信息,详参“关于sqlite_master表”一节。
数据库中第一个页(page1)永远是Btree页。
Page1的前100个字节是一个对数据库文件进行描述的“文件头”。
它包括数据库的版本、格式的版本、页大小、编码等所有创建数据库时设置的永久性参数。
关于这个特殊文件头的文档在btreeInt.h中,具体格式如下:
偏移量
大小
说明
0
16
头字符串,如果不改源程序,此字符串永远是"
SQLiteformat3"
。
2
页大小(以字节为单位)。
18
1
文件格式版本(写)。
对于SQLite的当前版本,此值为1。
如果该值大于1,表示文件为只读。
SQLite将来版本对此域的规定可能改变。
19
文件格式版本(读)。
如果该值大于1,SQLite认为文件格式错,拒绝打开此文件。
20
每页尾部保留空间的大小。
(留作它用,默认为0。
21
Btree内部页中一个单元最多能够使用的空间。
255意味着100%,默认值为0x40,即64(25%),这保证了一个结点(页)至少有4个单元。
22
Btree内部页中一个单元使用空间的最小值。
默认值为0x20,即32(12.5%)。
23
Btree叶子页中一个单元使用空间的最小值。
SQLite的当前版本规定21~23的3个字节值只能是0X402020。
原来这3个字节值是可变的,从3.6.0版开始被固定下来了。
24
4
文件修改计数,通常被事务使用,由事务增加其值。
SQLite用此域的值验证内存缓冲区中数据的有效性。
28
未使用。
32
空闲页链表首指针。
参“空闲页”一节。
36
文件内空闲页的数量。
40
60
15个4字节的元数据变量。
从偏移40开始的15个4字节元数据变量在btreeInt.h中的定义如下:
404Schema版本:
每次schema改变(创建或删除表、索引、视图或触发器等对象,造成sqlite_master表被修改)时,此值+1。
444Fileformatofschemalayer:
当前允许值为1~4,超过此范围,将被认为是文件格式错。
484Sizeofpagecache。
524Largestroot-page(auto/incr_vacuum):
对于auto-vacuum数据库,此域为数据库中根页编号的最大值,非0。
对于非auto-vacuum数据库,此域值为0。
5641=UTF-8、2=UTF16le、3=UTF16be。
604Userversion。
此域值供用户应用程序自由存取,其含义也由用户定义。
644Incrementalvacuummode:
对于auto-vacuum数据库,如果是Incrementalvacuum模式,此域值为1。
否则,此域值为0。
684未使用。
724未使用。
用UltraEdit打开文件foods_test.db,page1在0X0000~0X03FF。
其中文件头内容如下(深蓝色部分):
前16个字节为头字符串,程序中固定设为"
0X0400:
页大小,0X0400=1024字节。
0X01:
文件格式版本(写),值为1。
文件格式版本(读),值为1。
0X40:
0X40=64,即25%。
0X20:
0X20=32,即12.5%。
0X00000003:
文件修改计数,现在已经修改了3次,分别是1次创建和两次插入。
从0X20开始的4个字节:
当前值为0,表示该链表为空。
从0X24开始的4个字节:
当前值为0。
从0X28开始的4个字节:
Schemaversion。
当前值为0X00000001。
以后,每次sqlite_master表被修改时,此值+1。
从0X38开始的4个字节:
采用的字符编码。
此处为0X00000001,表示采用的是UTF-8编码。
注意:
在SQLite文件中,所有的整数都采用大端格式,即高位字节在前。
2.3Btree页格式介绍
2.3.1Btree页的分区
页的类型有Btree页、空闲页和溢出页,本文前3章介绍的都是Btree页,其他类型的页在第4、5章介绍。
每个Btree页由四个部分构成:
1.页头
2.单元指针数组
3.未分配空间
4.单元内容区
首先介绍“单元”的概念:
Btree页内部以单元(cell)为单位来组织数据,一个单元包含一个(或部分,当使用溢出页时)payload(也称为Btree记录)。
由于各类数据大小各不相同,每个单元的大小也就是可变的,所以Btree页内部的空间需要进行动态分配(程序内部动态分配,不是动态申请空间),单元是Btree页内部进行空间分配和回收的基本单位。
页内所有单元的内容集中在页的底部,称为“单元内容区”,由下向上增长。
由于单元的大小可变,因此需要对每个单元在页内的起始位置(称为单元指针)进行记录。
单元指针保存在单元指针数组中,位于页头之后。
单元指针数组包含0个或多个指针,由上向下增长。
单元指针数组和单元内容区相向增长,中间部分为未分配空间。
系统尽量保证未分配空间位于最后的指针之后,这样,就很容易增加新的单元,而不需要整理碎片。
单元不需要是相邻和有序的,但单元指针是相邻和有序的。
每个指针占2个字节,表示该单元在单元内容区中距页开始处的偏移。
页中单元的数量保存在页头中。
2.3.2页头格式
页头包含用来管理页的信息,它通常位于页的开始处。
对于数据库文件的page1,页头始于第100个字节处,因为前100个字节是文件头(fileheader)。
页头的格式如下:
页类型标志。
1:
intkey,2:
zerodata,4:
leafdata,8:
leaf。
第1个自由块的偏移量。
3
本页的单元数。
5
单元内容区的起始地址。
7
碎片的字节数。
8
最右儿子的页号(thePtr(n)value)。
仅内部页有此域。
下面对页头各域分别进行介绍。
页类型标志:
如果leaf位被设置,则该页是一个叶子页,没有儿子;
如果zerodata位被设置,则该页只有关键字,而没有数据;
如果intkey位被设置,则关键字是整型;
如果leafdata位设置,则tree只存储数据在叶子页。
以上内容见于大多数SQLite介绍性文档,btreeInt.h中也这么说。
但通过分析程序代码,并从参考文献6中得到确认,结论如下:
上述描述与实际实现是矛盾的。
可以这样理解:
就不用管各标志位的含义了,如果是B+tree的叶子页,该字节值为0X0D,如果是B+tree的内部页,该字节值为0X05,如果是B-tree的叶子页,该字节值为0X0A,如果是B-tree的内部页,该字节值为0X02。
由此可见:
intkey标志倒是可以作为判断B+tree树和B-tree的标志(置1为B+tree树),程序中实际也是这样应用的。
第1个自由块的偏移量:
由于随机地插入和删除单元,将会导致一个页上单元和空闲区域互相交错。
单元内容区域中没有使用的空间收集起来形成一个空闲块链表,这些空闲块按照它们地址的升序排列。
页头偏移为1的2个字节指向空闲块链表的头。
每个空闲块至少4个字节,因为一个空闲块的开始4个字节存储控制信息:
前2个字节指向下一个空闲块(0意味着没有下一个空闲块了),后2个字节为该空闲块的大小。
碎片的字节数:
由于空闲块大小至少为4个字节,所以单元内容区中的3个字节或更小的自由空间(称为碎片,fragment)不能存在于空闲块列表中。
所有碎片的总的字节数将记录在页头偏移为7的位置(碎片最多为255个字节,在它达到最大值之前,页会被整理)。
单元内容区的起始地址:
单元内容区的起始地址记录在页头偏移为5的地方。
这个值为单元内容区域和未使用区域的分界线。
最右儿子的页号:
如果本Btree页是叶子页,则无此域,页头长为8个字节。
如果本Btree页为内部页,则有此域,页头长为12个字节。
页头偏移为8的4个字节包含指向最右儿子的指针,该指针的含义将在第2章介绍。
有关Btree页格式的其它规定,将在下一节中用到时再介绍。
2.4Page1格式分析
Btree的基本原理这里就不详细介绍了。
Btree有多种实现方法,各类《数据结构》教材中的介绍也各不相同,但原理大同小异,随便找一本参考一下吧。
最简单的Btree只有一个结点,既是根页,也是叶子页。
当前foods_test.db(大小为2K)只有两个页,都是Btree页。
每个页都是一个Btree(B+tree,因为存储的是表数据),都是上述的单结点Btree。
其中page1为系统表sqlite_master的根页,下面我们对该页进行详细分析。
2.4.1页头分析
该页的页头从0X64=100处开始(前面100个字节是文件头),8个字节(因为是叶子页)。
如下图中深蓝色部分所示:
说明:
0X0D:
说明该页为B+tree的叶子结点。
0X0000:
值为0,说明当前自由块链表为空。
0X0001:
当前sqlite_master表中只有一条记录,所以本页当前只有1个单元。
0X0399:
0X00:
2.4.2单元指针数组
单元指针数组在页头之后,当前只有一个指针,为0X0399。
2.4.3关于可变长整数
可变长整数是SQLite的特色之一,使用它既可以处理大整数,又可以节省存储空间。
由于单元中大量使用可变长整数,故在此先加以介绍。
可变长整数由1~9个字节组成,每个字节的低7位有效,第8位是标志位。
在组成可变长整数的各字节中,前面字节(整数的高位字节)的第8位置1,只有最低一个字节的第8位置0,表示整数结束。
可变长整数可以不到9个字节,即使使用了全部9个字节,也可以将它转换为一个64-bit整数。
下面是一些可变长整数的例子:
0x00转换为0x00000000
0x7f转换为0x0000007f
0x810x00转换为0x00000080
0x820x00转换为0x00000100
0x800x7f转换为0x0000007f
0x8a0x910xd10xac0x78转换为0x12345678
0x810x810x810x810x01转换为0x10204081
可变长整数可用于存储rowid、字段的字节数或Btree单元中的数据。
2.4.4关于sqlite_master表
sqlite_master是一个系统表,保存了数据库的schema信息。
在逻辑上sqlite_master包含5个字段,如下表所示:
编号
字段
1
type
值为"
table"
、"
index"
trigger"
或"
view"
之一。
2
name
对象名称,值为字符串。
3
tbl_name
如果是表或视图对象,此字段值与字段2相同。
如果是索引或触发器对象,此字段值为与其相关的表名。
4
rootpage
对触发器或视图对象,此字段值为0。
对表或索引对象,此字段值为其根页的编号。
5
SQL
字符串,创建此对象时所使用的SQL语句。
2.4.5B+tree叶子页的单元格式
单元是变长的字节串。
一个单元包含一个(或部分,当使用溢出页时)payload。
B+tree叶子页单元的结构如下:
var(1–9)
Payload大小,以字节为单位。
数据库记录的Rowid值。
*
Payload内容,存储数据库中某个表一条记录的数据。
溢出页链表中第1个溢出页的页号。
如果没有溢出页,无此域。
结合实例来说明吧。
当前的单元内容区中只有一个单元,从0X0399开始,内容如下图所示:
0X65:
Payload数据的字节数。
可以看出Payload数据是从0717~2029。
foods(table对象)在sqlite_master表中对应记录的rowid,值为0X01。
Payload的格式如下图所示:
每个payload由两部分组成。
第1部分是记录头,由N+1个可变长整数组成,N为记录中的字段数。
第1个可变长整数(header-size)的值为记录头的字节数。
跟着的N个可变长整数与记录的各字段一一对应,表示各字段的数据类型和宽度。
用可变长整数表示各字段类型和宽度的规定如下表所示:
类型值
含义
数据宽度(字节数)
NULL
Nin1..4
有符号整数
N
6
8
7
IEEE符点数
8-11
未使用
N/A
N>
12的偶数
BLOB
(N-12)/2
13的奇数
TEXT
(N-13)/2
header-size的值包括header-size本身的字节和Type1~TypeN的字节。
Data1~DataN为各字段数据,与Type1~TypeN一一对应,类型和宽度由Type1~TypeN指定。
本例的payload数据为:
0X07:
记录头包括7个字节。
0X17:
字段1。
TEXT,长度为:
(23-13)/2=5。
值为:
table。
字段2。
foods。
字段3。
字段4。
整数,长度为1。
0X02。
表示本表B+tree的根页编号为2。
0X8129:
字段5。
TEXT。
0X8129为可变长整数,转换为定长为0XA9=169。
可知字段长度为:
(169-13)/2=78=0X4E。
对应数据为下图中蓝色部分。
2.5Page2格式分析
Page2为表foods的根页。
foods的Btree只有一个结点,既是根页,也是叶子页。
有了page1的分析经验,page2就好分析了。
作为对上一节内容的巩固,再简单分析一下吧。
用UltraEdit打开文件foods_test.db,page2在0X0400~0X07FF。
本页不再是文件首页(没有文件头),所以页头起始于本页的开始处,其内容如下(深蓝色部分):
因为是Btree的叶子页,所以页头只有8个字节。
0,说明当前自由块链表为空。
0X0002:
本页
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- SQLite 文件格式 分析 v102
![提示](https://static.bdocx.com/images/bang_tan.gif)