1.数据库文件格式
在深入到sqlite引擎的细节之前,我首先在以下两小节分别说明一下数据库命名惯例和数据库文件结构。
2.1数据库命名惯例
当应用试图通过sqlite3_open API函数打开一个数据库时,都需要传递给该函数数据库文件的名字。文件名可以是参考当前工作目录的相对路径,或者从系统文件树的根结点开始的绝对路径名。所有可以被地文件系统接受的常规文件名都不错。但是,有两个需要注意的例外:
l 如果文件名是C语言的空指针(即,0),sqlite会创建并打开一个临时文件
l 如果文件名是”:memory:”,sqlite会创建一个内存数据库
在这两种情况下,当应用关闭数据库连接时,数据库消失,即,数据库不具有持久性
注
sqlite给临时文件命的名是随机的。命名以sqlite_开头,后面接16个数字或者字母组成的字符串。这些文件存储在一个标准的本地临时文件目录。sqlite依次按照(1)/var/tmp、(2)/usr/tmp、(3)/tmp、(4)当前工作目录的顺序,选择临时文件存放的目录。
不管是上述提到的哪种方式打开的数据库(文件、临时数据库或者内存数据库),在sqlite内部都称之(不论已创建与否)为main数据库。
注
在sqlite内部,数据库文件名不是数据库名。sqlite里它们俩是不同但是有关联的概念。通过attach命令,你可以关联同一个数据库文件到一个数据库连接,作为不同的数据库名。你可以通过这些不同的文件名,申请操作这同一个数据库文件。你可以参考sqlite的官网以了解更多关于attach的语义信息。
sqlite为每一个通过sqlite3_open打开的数据库连接维护一个单独的临时数据库。临时数据库存储临时对象,例如表和它们的索引。(应用可以在它们的数据库操作中,使用着两个名字,即main和temp。例如,select * from temp.table1返回temp数据库中table1的所有行,而不是main数据库中。Temp数据库的目录表名是sqlite_temp_master。)临时的对象仅仅在同一个数据库连接中可见(而不是在同一线程、进程或者不同进程连接到同一个数据库文件的其它连接中)。sqlite将temp数据库存放在一个区别于主数据库文件的单独临时文件中。当应用关闭和main数据库的连接时,临时文件将被删除。
2.2数据库文件结构
除内存数据库之外,sqlite都会将整个数据库(main或者是temp)存放在单个数据库文件中。
为了方便空间管理和从数据库中读写数据,sqlite将每个数据库(包括内存数据库)划分到固定尺寸的区域。这种区域叫做数据库页,或者简称页。页尺寸是2的指数大小。可以在512到32768之间(也包含这两个边界值);默认值是1024.(在编码和外部存储的多种场合中,存储页尺寸的上限受限于有符号的2字节变量的表示范围。数据库是一个可伸缩的存储页组。存储页组的索引称作页号。页号从1开始,可以增至2,147,483,647(2^32-1)。(上限可能受限于本地操作系统的最大文件尺寸限制。)0号页被当做空页——物理存储上就没有这一页。从文件偏移0的位置开始,1号页和后续页一页接一页存在数据库文件中。
注:
一旦数据库文件被创建,sqlite使用编译时默认尺寸,但是在数据库中创建第一个表之前,尺寸可以通过pragma命令改变。sqlite存储的该尺寸值被当做元数据的一部分。它将使用该尺寸值代替默认值。(前面提到过,pragma命令被用作修改sqlite库的行为。详情参看sqlite的官网。)
有四种类型的存储页:叶子页、内部页、溢出页和空白页。空白页是非活动(当前未被使用)页;其他的都是活动页。B+树的内部页包含搜索的导航信息(B-树内部页有搜索辅助信息和实际数据)。B+树的叶子页存储实际数据(例如,表中的一行行数据)。如果一行的数据太大,一页容不下,一部分就存在树上的页里,另一部分就存在溢出页里。
sqlite可以为任何页类型分配任意数据库页,除了1号页,永远是B+树的内部页。这一页在偏移为0的位置,也保存着一个100字节的文件头记录。头信息描述了数据库文件的结构特征。当创建文件时,sqlite初始化头信息。文件头的格式如下表所述。头两列的单位是字节。
偏移 |
尺寸 |
描述 |
0 |
16 |
头字符串 |
16 |
2 |
页尺寸(字节数) |
18 |
1 |
文件格式写版本 |
19 |
1 |
文件格式读版本 |
20 |
1 |
每页末尾保留的字节数 |
21 |
1 |
最大嵌入负载片段 |
22 |
1 |
最小嵌入负载片段 |
23 |
1 |
最小叶负载片段 |
24 |
4 |
文件变更计数器 |
28 |
4 |
保留作以后扩展用 |
32 |
4 |
空白页表的第一页 |
36 |
4 |
空白页的个数 |
40 |
60 |
15个四字节的元值 |
以下是头中每个元素的详细描述:
头字符串:
是一个16字节的字符串:“sqlite format 3.”
页尺寸
数据库中每一页的尺寸
文件格式
偏移18和19字节的两个字节被用作标明文件格式版本。在当前版本的sqlite中,它俩都应该固定值为1,否则将返回一个错误。如果将来出现文件格式变更,这些书将递增到新的文件格式版本号。
保留空间
sqlite可能会在每个页的末尾保留一个很小的固定大小的空间,以作内部使用,并且内部偏移为20;默认值是0.当使用sqlite内建的加密技术时,它将变为非零值。页剩下的部分(页尺寸减去保留空间的尺寸)是可用空间,数据库的内容就存在这。
嵌入负载
最大嵌入负载片段值(偏移21的位置)是页内由标准B/B+树内部结点的单条目(或者称为单元或记录)可占用的总空间。值255表示100%可用。默认最大嵌入负载片段值是64(即,25%):该值用于限制最大单元尺寸,以使至少一个结点上有4个单元。如果一个单元上的负载大于最大值,额外的负载就会在溢出页存储。一旦sqlite分配了一张溢出页,它就会移动尽可能多的字节到溢出页中存储,只需保证单元的尺寸不小于最小嵌入负载片段值(偏移22的位置)。默认值是32,即12.5%。
最小页负载片段值(偏移23的位置)跟最小嵌入负载片段值差不多,除了它的定义是B+树的叶子页。默认值也是32,即12.5%。叶结点的最大负载片段值总是100%(即255),所以未在头中明确标出。(在B树中,没有特殊用途的叶结点。)
文件变更计数器
文件变更计数器(偏移24的位置)被用于事务处理。该值将被每一个事务递增。本打算用它标明当数据库改变时,存储页管理器可以避免清空它的缓存。不过这个特性还未在写这本书的时候实现。存储页管理器负责递增该值。
空白页表
空白页表头存储在文件头偏移32的位置。总空白页数存在偏移36的位置。空白页表由有根的枝干结构构成(见图2-1)。空白页表的页有两种子类型:枝干页和叶子页。文件头指向枝干链表的第一个枝干。每个枝干页指向多个叶子页。(叶子页中的内容信息未在枝干页中标明。)
图2-1 空白页表的结构
枝干页的格式如下,由页地址的基地址开始:
l 一个代表下一枝干页的4字节大小的页号
l 一个四字节整型值,标明存储在该页的叶子页指针数目
l 0或者多个4字节大小的页号,标明叶子页
当某一页处于非活动状态时,sqlite将它加到空白页表中,并不释放到本地文件系统。当你往数据库中加入新的信息时,sqlite从空白页表中抽取空白页存储这些新信息。如果空白页表为空,sqlite从本地文件系统获取新页,并且把它们附加到数据库文件末尾。
注
你可以通过对数据库执行vacuum指令,释放掉空白页表。该指令拷贝数据库到临时文件(拷贝是用的INSERT INTO… SELECT * FROM… 命令)。然后,在一个事务处理中,使用临时拷贝重写原来的数据库。【为什么不直接释放?】
元变量
在偏移40的位置,由15个4字节整型值,为B+树和虚拟机模块保留。它们代表许多元变量的值,包括在偏移40位置的数据库模式的cookie标号;该值将在每一次模式变更时递增。其他的元变量包括:在偏移44位置的模式层文件格式信息,48偏移处的页缓存尺寸,52偏移处的自动vacuum标志,56偏移处的字符编码(1:UTF-8,3:UTF-16 LE,4:UTF-16 BE) 和60偏移处的用户版本号。你可以从sqlite的源文件中获取这些变量更多的信息,尤其是btree.c。
注
sqlite的数据库文件格式向后兼容至3.0.0版本。这意味着sqlite的任何版本可以读写原来由3.0.0版本的sqlite创建的数据库文件。大多数情况下反方向也是对的---3.0.0版本的sqlite基本上可以正常读写被之后版本的sqlite库创建的数据库。然而,有一些被后续版本引入的新特性3.0.0版本不支持,如果数据库包含这些可选的新特性,老版本的库将不能读取和理解它。
Page1里,文件头后就是一个B+树的内部结点。这个节点就是主目录表的根结点,针对常规(主或者附加)或者临时数据库,分别命名为sqlite_master或者sqlite_temp_master。
注
原文链接:https://www.f2er.com/sqlite/199029.html