引子
书接上回,前面看了数据表B树叶子节点。接下来要看一看数据表B树内部节点。数据表B树内部节点是4种B树节点中结构最简单的,为什么?
它没有payload。它里面的单元不包含具体的记录数据,只包含关键值与左孩子指针,在逻辑上形成树形结构。
正式开始
要想得到一个数据表B树内部节点,最简单的办法就是向sqlite数据表中多插入一些数据,当数据在一页内放不下时,内部节点就出现了。B树的深度也就增长了。学过数据结构的人都知道。B树是一个平衡树。通过把线性查找变成树查找,查找效率提高了指数倍。所以任何一个像样一点的数据库产品,都会使用B树来提高查找效率。
这次选一个内容多一点的数据库,它的大小有41k。按照上一篇讲的老办法,先将数据库文件切分成多个独立的页文件。
还是先拿第一页做实验,下图是对第一页进行分析得到的结果
PageNo -> 1
0x0000 -> Flags : 13[0x000d][TableBTreeLeaf]
0x0001 -> offseFirstFreeblock : 789
0x0003 -> numberOfCells : 4
0x0005 -> firstByteOfCellContent : 472[0x01d8]
0x0007 -> numberOfFragmentedFreeBytes : 0
0x0008 -> RightChildPageNo : 0
cellPointArrayOffset->108[0x006c]
cellCount->4
col1 type:text size:5 -> table
col2 type:text size:15 -> sqlite_sequence
col3 type:text size:15 -> sqlite_sequence
col4 type:i8 size:1 -> 3
col5 type:text size:38 -> CREATE TABLE sqlite_sequence(name,seq)
col1 type:text size:5 -> table
col2 type:text size:8 -> customer
col3 type:text size:8 -> customer
col4 type:i8 size:1 -> 4
col5 type:text size:203 -> CREATE TABLE customer
(id INTEGER PRIMARY KEY AUTOINCREMENT ,
customerId TEXT NOT NULL,
customerName TEXT NOT NULL,
idCard TEXT ,
remark TEXT
)
col1 type:text size:5 -> table
col2 type:text size:12 -> tmp_customer
col3 type:text size:12 -> tmp_customer
col4 type:i8 size:1 -> 2
col5 type:text size:88 -> CREATE TABLE tmp_customer(customerId text,customerName text,logTime text,netWeight text)
col1 type:text size:5 -> index
col2 type:text size:6 -> idcard
col3 type:text size:8 -> customer
col4 type:i8 size:1 -> 5
col5 type:text size:51 -> CREATE INDEX "idcard"
ON "customer" ("idCard" ASC)
从上面的结果来看,第1页的页标志是0x0d,它还是一个数据表B树叶子节点。因为大部分的数据都插到了一个叫做customer的表中,并没有在sql_master表中增加什么内容。
我们接着查看上面结果中的单元部分,会发现如下内容
col1 type:text size:5 -> table
col2 type:text size:8 -> customer
col3 type:text size:8 -> customer
col4 type:i8 size:1 -> 4
col5 type:text size:203 -> CREATE TABLE customer
(id INTEGER PRIMARY KEY AUTOINCREMENT ,
customerId TEXT NOT NULL,
customerName TEXT NOT NULL,
idCard TEXT ,
remark TEXT
)
注意到 “col4 type:i8 size:1 -> 4” 这说明customer表的根页号是4。而我们已经往customer表中插入了不少数据。因此页面4很可能是一个内部节点。
那让我们来检查一下这个4号页,看看它的庐山真面目吧。
一个数据表B树内部节点
PageNo -> 4
0x0000 -> Flags : 5[0x0005][TableBTreeInterior]
0x0001 -> offseFirstFreeblock : 0
0x0003 -> numberOfCells : 4
0x0005 -> firstByteOfCellContent : 1004[0x03ec]
0x0007 -> numberOfFragmentedFreeBytes : 0
0x0008 -> RightChildPageNo : 38
cellPointArrayOffset->12[0x000c]
cellCount->4
leafPageNo->6[0x0006]
cellKey->24
leafPageNo->25[0x0019]
cellKey->48
leafPageNo->35[0x0023]
cellKey->72
leafPageNo->36[0x0024]
cellKey->96
从显示的结果的第二行可以看出,这正是一个数据表B树内部节点。它包含4个单元,它的最右孩子是第38页
它的4个单元的内容是:
eafPageNo->6[0x0006]
cellKey->24
leafPageNo->25[0x0019]
cellKey->48
leafPageNo->35[0x0023]
cellKey->72
leafPageNo->36[0x0024]
cellKey->96
这4个单元分别指向6号页,15号页,35号与36号页,再加上它的最右孩子38号页,说明这个内部节点有5个孩子。
下面看一下实现分析功能的代码
数据表B树内部节点分析函数
//解包表B树(B+树)内部节点的单元内容,内部节点的结构比较简单,不用再调用其它函数进行分解
function unpackTableBTreeInteriorCell($workBench){
//参数区开始------------------------------------
$fin = $workBench['fin'];
$pageNo = $workBench['pageNo'];
$cellNo = $workBench['cellNo'];
$cellOffset = $workBench['cellOffset'];
$outputPath = $workBench['outputPath'];
//参数区结束------------------------------------
fseek($fin, $cellOffset,SEEK_SET);
//找到cell入口后,第一部分是4字节整数,指向叶子节点的页号,第二部分是变长整数,代表关键字key(i)
$leafPageNo = readValueFromFile($fin, 4,'u32',$info);
echo 'leafPageNo->',intWithHexStr($leafPageNo),"\n";
$cellKey = readValueFromFile($fin, 'var','varInt',$info);
echo 'cellKey->',$cellKey,"\n";
$resultArray = ['leafPageNo'=>$leafPageNo,'cellKey'=>$cellKey];
//设置输出结果的文件名
$resultFileName = $outputPath .'/page'.$pageNo.'-cell'.$cellNo.'-payload.json';
//echo "\nunpack over!";
//echo "\n\n\n";
//将结果以json形式写入文件
file_put_contents($resultFileName, json_encode($resultArray));
}
这是4种节点分析中最简单的。从代码上也可以看得出来,不需要再调用对payload的解析。因为它的单元中根本就不包含payload信息