直到第九步结束,我们只可以完成一个根节点和两个孩子节点的数据结构,并且能够成功利用select读取到正确的数据表,然而当我们再次插入新的数据后会发现,我们在拆分叶节点时由于已存在根节点,不能讲拆分后的旧结点作为新的根节点,而应该拆分后成为新的两个叶节点并更新根节点,具体需要怎么做呢?首先我们需要修改leaf_node_split_and_insert()函数:
修改分割算法
//创建新的叶子结点并分割当前节点元素
void leaf_node_split_and_insert(Cursor* cursor,uint32_t key,Row* value){
void* old_node=get_page(cursor->table->pager,cursor->page_num);//获取当前节点
uint32_t old_max=get_node_max_key(old_node);//获取旧节点的最大关键词
uint32_t new_page_num=get_unused_page_num(cursor->table->pager);//从页面缓存器中获取未使用页面编号
void *new_node=get_page(cursor->table->pager,new_page_num);//根据未使用的页面编号创建新节点
initialize_leaf_node(new_node);//初始化新节点为叶子结点
*node_parent(new_node)=*node_parent(old_node);//使new_node和old_node指向同一个父节点
*leaf_node_next_leaf(new_node)=*leaf_node_next_leaf(old_node);//新节点的兄弟为旧节点的兄弟
*leaf_node_next_leaf(old_node)=new_page_num;//旧结点的兄弟为新节点
//将旧节点的元素一分为二,序号小的放旧结点,需要大的放在新节点
for(int32_t i=LEAF_NODE_MAX_CELLS;i>=0;i--){
void* destination_node;
if(i>=LEAF_NODE_LEFT_SPLIT_COUNT){
destination_node=new_node;
}else{
destination_node=old_node;
}
uint32_t index_within_node=i%LEAF_NODE_LEFT_SPLIT_COUNT;//重新定义元胞编号
void *destination=leaf_node_cell(destination_node,index_within_node);//获取元胞应存入的地址
if(i==cursor->cell_num){
serialize_row(value,leaf_node_value(destination_node,index_within_node));
*leaf_node_key(destination_node,index_within_node)=key;
}else if(i>cursor->cell_num){
memcpy(destination,leaf_node_cell(old_node,i-1),LEAF_NODE_CELL_SIZE);//新节点编号从0开始
}else{
memcpy(destination,leaf_node_cell(old_node,i),LEAF_NODE_CELL_SIZE);
}
}
//更新新旧节点的元胞数
*(leaf_node_num_cells(old_node))=LEAF_NODE_LEFT_SPLIT_COUNT;//旧结点中大于LEAF_NODE_LEFT_SPLIT_COUNT编号的元素未删除,只更新元胞数
*(leaf_node_num_cells(new_node))=LEAF_NODE_RIGHT_SPLIT_COUNT;
//需要更新节点的父节点
if(is_node_root(old_node)){
return create_new_root(cursor->table,new_page_num);//若旧结点为根节点,则创建新节点充当父节点
} else{
uint32_t parent_page_num=*node_parent(old_node);//提取旧节点页码编号
uint32_t new_max=get_node_max_key(old_node);//提取旧节点最大关键词
void* parent=get_page(cursor->table->pager,parent_page_num);
update_internal_node_key(parent,old_max,new_max);//更新内节点关键词从old_max->new_max
internal_node_insert(cursor->table,parent_page_num,new_page_num);//插入内节点
return;
}
}
重写内节点搜索
然而,其中的update_internal_node_key()函数和internal_node_insert()我们并未定义,但是我们知道他们应该实现什么样的功能。所以我们将他们补全。并且我们还重写了internal_node_find()函数变为两个部分,第一部分适用于虚招key所在内节点元胞编号的internal_node_find_child()函数,另一个是仍然来实现返回key对应叶子结点元胞位置。
//获得父节点所在页码编号
uint32_t *node_parent(void* node){return node+PARENT_POINTER_OFFSET;}
//更新内节点关键词
void update_internal_node_key(void* node,uint32_t old_key,uint32_t new_key){
uint32_t old_child_index=internal_node_find_child(node,old_key);//获取内节点中旧key位置
*internal_node_key(node,old_child_index)=new_key;//为旧key赋新值
}
//查找内节点中小于等于key所对应的元胞编号
uint32_t internal_node_find_child(void *node,uint32_t key) {
uint32_t num_keys = *internal_node_num_keys(node);//获取内节点关键词数量
uint32_t min_index = 0;
uint32_t max_index = num_keys;
//二分搜索
while (min_index != max_index) {
uint32_t index = (min_index + max_index) / 2;
uint32_t key_to_right = *internal_node_key(node, index);
if (key_to_right >= key) {
max_index = index;
} else {
min_index = index + 1;
}
}
return min_index;
}
//二分搜索键对应的胞元所在位置(cursor)
Cursor *internal_node_find(Table* table,uint32_t page_num,uint32_t key){
void* node=get_page(table->pager,page_num);
uint32_t child_index=internal_node_find_child(node,key);
uint32_t child_num=*internal_node_child(node,child_index);
void* child =get_page(table->pager,child_num);//读取对应孩子节点
//如果孩子节点为叶子结点返回对应key所在胞元编号,如果是内节点,则递归搜索直到找到叶子结点
switch (get_node_type(child)) {
case NODE_LEAF:
return leaf_node_find(table,child_num,key);
case NODE_INTERNAL:
return internal_node_find(table,child_num,key);
}
}
内节点插入
我们再来看一下internal_node_insert()函数,该函数实现了插入一个孩子指针/关键词对到父节点的功能
//添加一个孩子指针/关键词对到父节点
void internal_node_insert(Table* table,uint32_t parent_page_num,uint32_t child_page_num){
void* parent=get_page(table->pager,parent_page_num);//获得父节点地址
void* child=get_page(table->pager,child_page_num);//获得孩子节点地址
uint32_t child_max_key=get_node_max_key(child);//获得孩子节点中最大的关键词
uint32_t index=internal_node_find_child(parent,child_max_key);//找到内节点中关键词小于等于key的元胞编号
uint32_t original_num_keys=*internal_node_num_keys(parent);//获得父节点关键词数量
*internal_node_num_keys(parent)=original_num_keys+1;
//如果多余设定的关键词数量,我们需要开始拆分这个内节点
if(original_num_keys>=INTERNAL_NODE_MAX_CELLS){
printf("Need to implement splitting internal node\n");
exit(EXIT_FAILURE);
}
uint32_t right_child_page_num=*internal_node_right_child(parent);//获得父节点右孩子所在页码
void* right_child=get_page(table->pager,right_child_page_num);
if(child_max_key>get_node_max_key(right_child)){
//新增孩子节点若关键词比原右孩子关键词大,则替换右孩子
*internal_node_child(parent,original_num_keys)=right_child_page_num;//父节点最后一个孩子替换为原右孩子(原本为空)
*internal_node_key(parent,original_num_keys)=get_node_max_key(right_child);
*internal_node_right_child(parent)=child_page_num;
}else{
//将应插入key位置之后的所有元胞向后移动一个单元
for(uint32_t i=original_num_keys;i>index;i--){
void* destination = internal_node_cell(parent,i);
void* source=internal_node_cell(parent,i-1);
memcpy(destination,source,INTERNAL_NODE_CELL_SIZE);
}
*internal_node_child(parent,index)=child_page_num;//插入子孩子指针
*internal_node_key(parent,index)=child_max_key;//插入孩子节点关键词
}
}
后记
直到这里,我们的数据库已经可以实现两层B-Tree满载的情况。但是仍然不可以实现第二层叶节点转化为第二层内节点并生成第三层的叶节点的功能,这是我们下一步所要做的。然而直到这里,教程已经结束了。没有进一步的内容,其实学习到这里对sqlite的架构已经有了基本的了解,进一步向更复杂的数据库发展也是可以的。但是因为目前个人原因时间有限,还是选择直接再去研究sqlite的源码的基本框架。
一路上学习的源码和注释,已经放在了个人github上,感兴趣的小伙伴自行查看。