2021SC@SDUSC
pager模块把数据库的文件抽象成了基于页的文件。而B+树模块在它之上抽象出基于行的数据项。
然后表中的行可以通过很多种形式组织起来,比如输入顺序、相关性、哈希表、键值序列。
SQLite使用B+树来组织一个表中所有的行,不同的表有不同的B+树。
SQLite还把索引视为一个表,并且把索引存储在一个B树里,不同的索引有不同的B树。
B树和B+树很像,都是一种键值序列数据结构。SQLite不使用其他的组织行的方法。所以一个数据库就是一个B树和B+树的集合 。所有的这些树都是基于数据库页的,不能被分开。但是,不能有数据库页存储来自两个不同表的行。B/B+树模块的职责是组织树中的页,使得可以有效地存储和读取数据项。
一、BtShared结构分析
Btree结构中包含一个BtShared结构,包含了一个打开的数据库的所有页面的相关信息。
在系统运行中,有些数据库文件会被多个连接共享。这时,每个连接都会含有它所占有的这个数据库文件部分。而这个数据库文件的每个部分都会连接到相同的BtShared对象中。BtShared的定义在btreeInt.h中。
其结构如下所示:
struct BtShared {
Pager *pPager; /* The page cache */
BtCursor *pCursor; /* A list of all open cursors */
MemPage *pPage1; /* First page of the database */
u8 inStmt; /* True if we are in a statement subtransaction */
u8 readOnly; /* True if the underlying file is readonly */
u8 maxEmbedFrac; /* Maximum payload as % of total page size */
u8 minEmbedFrac; /* Minimum payload as % of total page size */
u8 minLeafFrac; /* Minimum leaf payload as % of total page size */
u8 pageSizeFixed; /* True if the page size can no longer be changed */
#ifndef SQLITE_OMIT_AUTOVACUUM
u8 autoVacuum; /* True if database supports auto-vacuum */
#endif
u16 pageSize; /* Total number of bytes on a page */
u16 usableSize; /* Number of usable bytes on each page */
int maxLocal; /* Maximum local payload in non-LEAFDATA tables */
int minLocal; /* Minimum local payload in non-LEAFDATA tables */
int maxLeaf; /* Maximum local payload in a LEAFDATA table */
int minLeaf; /* Minimum local payload in a LEAFDATA table */
BusyHandler *pBusyHandler; /* Callback for when there is lock contention */
u8 inTransaction; /* Transaction state */
int nRef; /* Number of references to this structure */
int nTransaction; /* Number of open transactions (read + write) */
void *pSchema; /* Pointer to space allocated by sqlite3BtreeSchema() */
void (*xFreeSchema)(void*); /* Destructor for BtShared.pSchema */
#ifndef SQLITE_OMIT_SHARED_CACHE
BtLock *pLock; /* List of locks held on this shared-btree struct */
BtShared *pNext; /* Next in ThreadData.pBtree linked list */
#endif
};
1:pPager
存储Btree页面缓存信息
2:pCursor
存储Btree中打开的一系列游标
3:pPage1
存放数据库文件的第一个页面
4:maxEmbedFrac:
Btree内部页中一个CELL最多能够使用的空间。255意味着100%,默认值为0x40(25%),这保证了一个页面至少包含4个CELL。
5:minEmbedFrac:
Btree内部页中一个CELL使用空间的最小值。默认值为0x20(12.5%)
6:minLeafFrac:
Btree叶子页中一个CELL使用空间的最小值。默认值为0x20(12.5%)
7:pageSize:
页面大小指示位(字节单位)
8:pageSizeFixed:
页面大小能否改变指示位
9:autoVacuum:
数据库是否支持autoVacuum指示位。
ps: autoVacuum数据库:当一个事务从数据库中删除了数据并提交后,数据库文件的大小保持不变。即使整页的数据都被删除,该页也会变成“空闲页”等待再次被使用,而不会实际地被从数据库文件中删除。执行vacuum操作,可以通过重建数据库文件来清除数据库内所有的未用空间,使数据库文件变小。但是,如果一个数据库在创建时被指定为auto_vacuum数据库,当删除事务提交时,数据库文件会自动缩小。使用auto_vacuum数据库可以节省空间,但却会增加数据库操作的时间。
10:usableSize
指示每个页面可用的字节数。一个页面 尾部可能保留一部分空间作为扩展,实际可以使用的页面字节数就是“页面大小-保留空间”。
11:maxLocal\ minLocal\maxLeaf\minLeaf
页面的一个CELL可以使用的最大。最小空间实际大小,通过下面公式可计算出来。
pBt->maxLocal = (pBt->usableSize-12)*pBt->maxEmbedFrac/255 - 23;
pBt->minLocal = (pBt->usableSize-12)*pBt->minEmbedFrac/255 - 23;
pBt->maxLeaf = pBt->usableSize - 35;
pBt->minLeaf = (pBt->usableSize-12)*pBt->minLeafFrac/255 - 23;
其中 12个字节为每个页面头的大小,23个字节为每个CELL内部的保存其它管理信息
2-byte 保存CELL的地址
4-byte 保存孩子节点的页号
9-byte 最多可以使用9个字节来保存Btree中的关键字值
4-byte 保存4个字节的数据值
4-byte 保存overflow 页面号
我们可以看到23个字节是最大可能使用的管理空间,根据配置不同实际不会使用到23个字节。
12:pBusyHandler:
保存客户端提供的数据库忙的回调函数句柄,用于忙时的回调。
13:inTransaction
存在TRANS_NONE\TRANS_READ\TRANS_WRITE三种状态,由于可能存在多个用户共享一个Btree结构,在TRANS_WRITE状态下只能有一个用户能够进行写事务。在TRANS_READ 状态下任意多个用户能进行读事务。
14:pLock
一个表级锁的链表
15:pNext
一个线程可能打开不同的数据库,该结构表示该线程保存的一系列的Btree数据。
二、内存中的页
数据库中加载到内存中的页的定义位于btreeInt.h,其结构如下:
struct MemPage {
u8 isInit; /* True if previously initialized.MUST BE FIRST! */
u8 nOverflow; /* Number of overflow cell bodies inaCell[] */
u8 intKey; /* True if table b-trees. False for index b-trees */
u8 intKeyLeaf; /* True if the leaf of an intKey table*/
u8 noPayload; /* True if internal intKey page (thusw/o data) */
u8 leaf; /* True if a leaf page */
u8 hdrOffset; /* 100 for page 1. 0 otherwise */
u8 childPtrSize; /* 0 if leaf==1. 4 if leaf==0 */
u8 max1bytePayload; /* min(maxLocal,127) */
u8 bBusy; /* Prevent endless loops on corruptdatabase files */
u16 maxLocal; /* Copy of BtShared.maxLocal orBtShared.maxLeaf */
u16 minLocal; /* Copy of BtShared.minLocal orBtShared.minLeaf */
u16 cellOffset; /* Index in aData of first cell pointer*/
u16 nFree; /* Number of free bytes on the page*/
u16 nCell; /* Number of cells on this page,local and ovfl */
u16 maskPage; /* Mask for page offset */
u16 aiOvfl[5]; /* Insert the i-th overflow cell beforethe aiOvfl-th
** non-overflow cell */
u8 *apOvfl[5]; /* Pointers to the body of overflowcells */
BtShared *pBt; /* Pointer to BtShared that this page ispart of */
u8 *aData; /* Pointer to disk image of the page data */
u8 *aDataEnd; /* One byte past the end of usable data*/
u8 *aCellIdx; /* The cell index area */
u8 *aDataOfst; /* Same as aData for leaves. aData+4 for interior */
DbPage *pDbPage; /* Pager page handle */
u16 (*xCellSize)(MemPage*,u8*); /* cellSizePtr method */
void (*xParseCell)(MemPage*,u8*,CellInfo*);/* btreeParseCell method */
Pgno pgno; /* Page number for this page */
};
为了方便原地操作和从数据库读写数据,SQLite把每个数据库(包括内存数据库)分成大小固定的多个页。页的大小是2的指数,可以在512到32,768之间,默认大小是1024B。上界是由存储页大小的2-byte signed int 变量决定的。
数据库(可以扩展和压缩)是一个页组成的数组。页的索引叫做页号(page number)。页号从1开始,上界是2,147,483,674(231-1)。(上界可以更大,这取决与文件系统的限制)。第0页是虚页。第1页和之后的页存储在数据库文件中,存储偏移量是0,即从文件开头开始存储第1页。
页的种类有4种:叶子页,内部页,溢出页,自由页。自由页是非激活页(还没有使用的页),其他的是激活页。B+树内部页包含搜索信息(B树内部页包含搜索信息和数据)。B+树的叶子页存储实际的数据(例如:表中每行的数据)。如果一页不能盛放一行数据,数据的一部分会存储在B+树的叶子页,另一部分存储在溢出页中。
文件头
此部分参考资料:《Inside SQLite》
SQLite可以使用任何类型的页,除了页1,页1永远是B+树的内部页,保存有100byte的文件头信息,存储偏移量是0。文件头信息决定了该数据库文件的结构。当数据库文件被创建时,SQLite初始化文件头信息。文件头的格式如下表所示,前两列的存储单位是byte。
Header string:
这是一个16byte的string:”SQLite format 3.”
Page size:
页的大小。
File format:
偏移量为18、19的两个byte用来存储文件格式版本,在当前版本中,这两个值都是1,否则将返回一个error值。如果将来文件格式发生改变,这两个值会增长以表示新的文件格式版本。
Reserved space:
处于一些原因,SQLite在每一页底部保留一小块固定空间(<=255bytes),空间的大小保存在reserveed space变量中,默认值是0。使用SQLite内建加密模式时,这个值会发生变化。
Embedded payload:
最大碎片负载(偏移量是21)指的是每页能够用于存储B/B+树内部节点的空间。255表示100%。默认值是64(25%),这个值用来限定cell的最大值,即每个节点最少有4个cell。如果cell的大小超过允许的最大值,SQLite就创建溢出页,把尽量多的byte移动到溢出页中,但是不能在分割的过程中导致cell的大小小于最小embedded payload(偏移量是22,默认值是32,即12.5%)。
叶子页最小负载(偏移量23)和最小embedded payload相似,但是是针对B+树的叶子页来说的。(默认值是32,12.5%)。叶子页的最大负载百分比永远是100%,文件头中并没有保存该值的变量。
File change counter:
文件更改次数(偏移量24)被用于事务管理。每个事务都会使这个值加1.这个值用来指示什么时候数据库被改变,辅助pager模块避免盲目刷新cache。截止到目前为止,这个功能还没有实现。Pager模块负责增加这个值。
Free list:
自由页链表(偏移量32)存储未使用的页。自由页页数存储在偏移量36。
在偏移量40的地方,有15个关于B+树和虚拟机模块的4bytes的整型变量。它们存储许多元信息,包括模式缓存号(偏移量40),每次模式变化会改变这个值。视图层的文件格式信息(偏移量44),page缓存大小(偏移量48),自动清空标志(偏移量52),文本编码(偏移量56,1:UTF-8, 3:UTF-16 LE, 4:UTF-16 BE),用户版本号(偏移量60)。在源文件btree.c中有关于这些变量更多的信息。
另外值得注意的是,所有的多字节整型变量都是大端存储的,这允许将数据库文件安全地从一个平台移动到另一个平台。