SQLite入门与分析(八)---存储模型(1)
写在前面:SQLite作为嵌入式数据库,通常针对的应用的数据量相对于通常DBMS的数据量是较小的。所以它的存储模型设计得非常简单,总的来说,SQLite把一个数据文件分成若干大小相等的页面,然后以B树的形式来组织这些页面。而对于大型的数据库管理系统,比如Oracle,或者DM ,存储模型要复杂得多。就拿Oracle来说吧,它对数据文件不仅从物理进行分块,而且从逻辑上进行分段,盘区和页的一个层次划分,DM也一样。不管怎么说,数据库文件要存储大量的数据,为了更好管理,查询和操作数据文件,DBMS不得不从物理上、逻辑上对数据文件的数据进行复杂的组织。本节主要讨论文件格式,下节讨论页面格式。
1、文件格式
1.1、数据库名称应用程序通过sqlite3_open API来打开数据库,该函数的一个参数为数据库文件的名称。SQLite内部命名为main数据库(除了临时数据库和内存数据库)。SQLite对每一个数据库都创建一个独立的文件。
在SQLite内部,数据文件名不是数据库名。SQLite对应用程序的每一个连接都维护着一个单独的临时数据库(temp数据库),临时数据库存临时对象,例如:表以及相应的索引。这些临时对象仅仅对同一个连接可见(对同一个线程,进程的其它连接是不可见的),SQLite存储临时数据库到一个单独的临时文件中,当应用程序关闭对main数据库的连接时,就删除临时文件。
1.2、数据库文件结构
除了内存数据库,SQLite把一个数据库(main和temp)都存储到一个单独的文件。
1.2.1、页面(page)
为了更好的管理和读/写数据库,SQLite把一个数据库(包括内存数据库)分成一个个固定大小的页面。页面大小的范围从512-32768(两者都包含),页面默认大小为1024个字节(1KB),实际上,页面的上限由2个字节的有符号整数决定。整个数据库可以看成这些页面的数组,页面数组的下标为页面的编号(page number),page number从1开始,一直到2,147,483,647 (2^31– 1)。实际上,数组上界还受文件系统允许的最大文件大小决定。0号页面视为空页面(NULL page),物理上不存在,1号页面从文件的0偏移处开始,一个页面接着下一个页面。
注:一旦数据库创建,SQLite使用编译时确定的默认的页面大小。当然,在创建第一个表之前,可以通过pragma命令改变页面大小。SQLite把该值作为元数据的一部分存储在文件中。
1.2.2、页面类型
页面(page)分四种类型:叶子页面(leaf),内部页面(internal),溢出页面(overflow)和空闲页面(free)。内部页面包含查询时的导航信息,叶子页面存储数据,例如元组。如果一个元组的数据太大,一个页面容纳不下,则一些数据存储在B树的页面中,余下的存储在溢出页面中。
1.2.3、文件头(file header)
作为文件开始的1号页面比较特殊,它包括100个字节的文件头。当SQLite创建文件时例初始化文件头,文件头的格式如下:
Structure of database file header |
||
Offset |
Size |
Description |
0 |
16 |
Header string |
16 |
2 |
Page size in bytes |
18 |
1 |
File format write version |
19 |
1 |
File format read version |
20 |
1 |
Bytes reserved at the end of each page |
21 |
1 |
Max embedded payload fraction |
22 |
1 |
Min embedded payload fraction |
23 |
1 |
Min leaf payload fraction |
24 |
4 |
File change counter |
28 |
4 |
Reserved for future use |
32 |
4 |
First freelist page |
36 |
4 |
Number of freelist pages |
40 |
60 |
15 4-byte meta values |
示例数据(100个字节):
53 51 4C 69 74 65 20 66 SQLite f 6F 72 6D 61 74 20 33 00 ormat 3. 04 00 01 01 00 40 20 20 .....@ 00 00 00 11 00 00 00 00 ........ 00 00 00 00 00 00 00 00 ........ 00 00 00 01 00 00 00 01 ........ 00 00 00 00 00 00 00 00 ........ 00 00 00 01 00 00 00 00 ........ 00 00 00 00 00 00 00 00 ........ 00 00 00 00 00 00 00 00 ........ 00 00 00 00 00 00 00 00 ........ 00 00 00 00 00 00 00 00 ........ 00 00 00 00 |
Header string(头字符串):
16个字节:"SQLite format 3."
Page size:
页面大小:0x04 00 ,即1024
File format:
文件格式:0x01 ,0x01,在当前的版本都为1。
Reserved space:
保留空间:0x00,1个字节,SQLite在每个页面的末尾都会保留一定的空间,留作它用,默认为0。
Embedded payload:
max embedded payload fraction(偏移21)的值限定了B树内节点(页面)中一个元组(记录,单元)最多能够使用的空间。255意味着100%,默认值为0x40,即64(25%),这保证了一个结点(页面)至少有4个单元。如果一个单元的负载(payload,即数据量)超过最大值,则溢出的数据保存到溢出的页面,一旦SQLite分配了一个溢出页面,它会尽可能多的移动数据到溢出页面,下限为min embedded payload fraction value(偏移为22),默认的值为32,即12.5% 。
min leaf payload fraction的含义与min embedded payload fraction类似,只不过是它是针对B树的叶子结点,默认值为32,即12.5%,叶子结点最大的负载为通常是100%,这不用保存。
File change counter:
文件修改计数,通常被事务使用,它由事务增加其值。该值的主要目的是数据库改变时,pager避免对缓存进行刷盘。
Freelist:
空闲页面链表,在文件头偏移32的4个字节记录着空闲页面链的第一个页面,偏移36处的4个字节为空闲页面的数量。空闲页面链表的组织形式如下:
空闲页面分为两种页面:trunk pages(主页面)和leaf pages(叶子页面)。文件头的指针指向空闲链表的第一个trunk page,每个trunk page指向多个叶子页面。
Trunk page的格式如下,从页面的起始处开始:
(1)4个字节,指向下一个trunk page的页面号;
(2)4个字节,该页面的叶子页面指针的数量;
(3)指向叶子页面的页面号,每项4个字节。
当一个页面不再使用时,SQLite把它加入空闲页面链表,并不从本地文件系统中释放掉。当添加新的数据到数据库时,SQLite就从空闲链表上取出空闲页面用来在存储数据。当空闲链表为空时,SQLite就通过本地文件系统增加新的页面,添加到数据库文件的末尾。
注:可以通过vacuum命令删除空闲链表,该命令通过把数据库中数据拷贝到临时文件,然后在事务的保护下,用临时文件中的复本覆盖原数据库文件。
Meta variables
元数据变量:从偏移为40开始,为15个4字节的元数据变量,这些元数据主要与B树和VM有关。如下:
** Meta values are as follows: ** meta[0] Schema cookie. Changes with each schema change. ** meta[1] File format of schema layer. ** meta[2] Size of the page cache. ** meta[3] Use freelist if 0. Autovacuum if greater than zero. ** meta[4] Db text encoding. 1:UTF-8 2:UTF-16LE 3:UTF-16BE ** meta[5] The user cookie. Used by the application. ** meta[6] ** meta[7] ** meta[8] ** meta[9] |
1.2.4、读取文件头
当应用程序调用API sqlite3_open打开数据库文件时,SQLite就会读取文件头进行数据库的初始化。
const char *zFilename, /* Name of the file containing the BTree database */
sqlite3 *pSqlite, /* Associated database handle */
Btree **ppBtree, /* Pointer to new Btree object written here */
int flags /* Options */
){
//读取文件头
sqlite3pager_read_fileheader(pBt->pPager, sizeof(zDbHeader), zDbHeader);
//设置页面大小
pBt->pageSize = get2byte(&zDbHeader[16]);
//…
}