Berkeley DB 1.8.6源代码学习(五)

来源:互联网 发布:知其雄,守其雌 编辑:程序博客网 时间:2024/04/25 04:50

bt_delete.c文件:删除B树中的数据;

__bt_delete函数:删除指定的记录;

int  __bt_delete(dbp, key, flags)

     const DB *dbp;

     const DBT *key;

         u_int flags;

参数列表:

dbp:数据库指针;

key:待删除记录的键值(DBT);

flags:删除方式,key指定的记录还是当前游标;

返回值:成功、失败、不确定(key未找到);

伪代码

获取数据库的Bt

取出tPIN页面;

如果r是只读树,则返回失败;

如果flags0,即删除键值为key的记录,则使用__bt_bdelete函数删除,成功删除后设置t已经修改标志,返回;

如果flagsR_CURSOR,即删除游标指定的记录,游标必须初始化,并且有效;

首先获取t的游标c;如果c没有初始化,则返回失败;

如果c被设置为CURS_ACQUIRECURS_AFTERCURS_BEFORE,表示当前的游标不可用,则返回不确定;

获取游标所在的页面h

如果h只含有一条记录,即删除游标指向的记录后h为空,此时需要删除h,删除页面需要有回溯用的祖先栈,使用__bt_stkacq重建祖先栈用于删除记录后删除页面之用;

使用__bt_dleaf函数删除游标指向的记录;

如果删除游标之后h为中页面,则使用__bt_pdelete函数删除h,否则将h置为需要写回标志MPOOL_DIRTY

 

__bt_stkacq函数:为删除游标入口项准备祖先栈;

static int  __bt_stkacq(t, hp, c)

         BTREE *t;

     PAGE **hp;

     CURSOR *c;

参数列表:

t:树指针;

hp:当前页面指针的指针,用于传入当前页面;

c:游标;

返回值:0表示成功,1表示失败;

伪代码

获取hp指向的页面h

取出hPIN标记;

使用__bt_search函数在树中查找游标指向记录的键值;如果找不到,则返回失败;否则返回找到的记录位置e

e所在的页面赋给h

如果h的编号与游标指向页面的编号相等,则取出hPIN标记,将h页下载的缓冲池中并通过hp返回;注意,本函数的功能是重构祖先栈,所以只要通过__bt_search函数找到游标指向的页面即可,此时__bt_search已经重构的B树的遍历栈;

如果h的页面编号不等于游标指向的页面编号,则说明当前树支持复键,且__bt_search函数找到的仅仅是匹配游标指向记录键值的众多记录中的一个,而没有定位到游标所在的页面;此时需要向右继续搜寻,直到找到游标所在的页面,在此过程中可能需要依照祖先栈回溯,才能移动到对应的右兄弟页面;具体操作如下:

1、如果h的右兄弟nextpg为空,转10

2、去除hPIN标记,准备回溯,初始化回溯次数level 0

3、使用BT_POP宏从栈中获取h的父记录parent

4、获取h的父结点页面,将其赋值给h,即h向上回溯;

5、如果parent记录不为最后一条索引,则将parent的下一条记录压入栈中;否则,去除hPIN标记,将回溯次数level递增;转3

6、如果level不等于0,当前栈指向的记录为内结点记录,则将level递减;否则转8

7、获取当前栈指向的记录bi,将bi所代表的页面入站;去除hPIN标志,将bi代表的页面取出赋给h,即栈增长,遍历下移;将h的第一条记录作为栈的当前记录,因为复键的记录如果不在统一个页面上,在必然在相邻页面的边缘处,可以参见页面分裂的策略;然后转6,继续向下遍历;

8、取出hPIN标记;获取nextpg页面赋给h

9、如果h的页面编号与游标指向的页面编号不相等,转1;否则,转10

10、如果h的页面编号与游标指向的页面编号相等,则去除hPIN标记,将h页下载的缓冲池中并通过hp返回;

如果上述操作不能得到成功的返回,可能是__bt_search函数在查找是没有将位置停留在复键第一条记录上,此时需要向作寻找游标,如果仍然失败,则只能返回失败;向左寻找的操作同向右寻找相似,具体如下:

首先取出hPIN标记;

使用__bt_search函数重置栈;将查找的记录所在的页面赋给h

1、如果h的左兄弟prevpg为空,转10

2、去除hPIN标记,准备回溯,初始化回溯次数level 0

3、使用BT_POP宏从栈中获取h的父记录parent

4、获取h的父结点页面,将其赋值给h,即h向上回溯;

5、如果parent记录不为第一条索引,则将parent的上一条记录压入栈中;否则,去除hPIN标记,将回溯次数level递增;转3

6、如果level不等于0,当前栈指向的记录为内结点记录,则将level递减;否则转8

7、获取当前栈指向的记录bi,将bi所代表的页面入站;去除hPIN标志,将bi代表的页面取出赋给h,即栈增长,遍历下移;将h的最后一条记录作为栈的当前记录,因为复键的记录如果不在统一个页面上,在必然在相邻页面的边缘处,可以参见页面分裂的策略;然后转6,继续向下遍历;

8、取出hPIN标记;获取prevpg页面赋给h

9、如果h的页面编号与游标指向的页面编号不相等,转1;否则,转10

10、去除hPIN标记,将h页下载的缓冲池中并通过hp返回;

 

__bt_bdelete函数:删除树中所有指定键值的记录;

static int  __bt_bdelete(t, key)

     BTREE *t;

     const DBT *key;

t:树指针;

key:指定的键值(DBT);

返回值:成功、失败、不确定(指定键值未找到);

伪代码

初始化已删除标志deleted 0

1、使用__bt_search函数查找键值为key的记录e,如果未找到,则根据deleted的值来判断返回值,如果deleted为真,则返回成功,否则返回失败;

2、从e的位置开始向前向后删除,如果树支持复键,这样保证将所有键值为key的记录全部删除;需要注意的是一旦删除到页面的边缘,则需要重新查找,防止其他页面有复键的存在;具体操作如下:

3、初始化重新查找标志redo0,将e所在的页面标记为h

4、使用__bt_dleaf函数删除记录e,需要注意的是,删除e之后,e指向的记录就变成了原记录的下一条记录(如果存在的话);

5、如果树不支持复键,则判断h是否为空,若是则使用__bt_pdelete函数删除h,若h不为空,则将h页面置为需写回;返回成功删除;

6、如果树支持复键,则需要继续删除,将deleted1

7、如果e存在且键值与key相等,转4

8、如果e到达h的边缘,则将redo1

9、开始向前删除复键记录,将e指向的位置前移一位;如果e到达页面的边缘,转14

10、比较e的键值不等于key,转14

11、使用__bt_dleaf函数删除e指向的记录;

12、如果e指向页面的第一条记录,将redo1,因为此时已经删除了页面的第一条记录,所以需要重新查找;

13、转9

14、如果h是空页;使用__bt_pdelete函数删除h,转1

15、去除hPIN标记;

16、如果redo为真,转1

 

__bt_pdelete函数:从树中删除一页;

static int __bt_pdelete(t, h)

     BTREE *t;

     PAGE *h;

参数列表:

t:树指针;

h:待删除的叶子结点页面;

返回值:成功、失败;

伪代码

沿着栈回溯,从父结点页面中删除h对应的记录,如果此时导致父页面为空,则删除父页面,如此操作直至遇到父结点页面删除后不为空或到达根结点;

初始化,以h为当前待删除的页面;

1、从栈中弹出当前待删除页面在父结点页面中的记录位置parent

2、取出当前待删除页面的父结点页面pg;将parentpg中的位置赋给index

3、获取当前待删除页面在父结点中的记录biBINTERNAL);

4、如果bi存储的是大数据,则使用__ovfl_delete函数删除bi的键值;

5、如果pg中只有一条记录,即当前待删除页面在pg中的记录,转6,否则转7

6、如果pg是根结点,则将pglowerupper指针恢复到初始化的状态,同时将pg改成叶子结点页面;转8;否则,使用__bt_relink从树中删除pg,然后使用__bt_free函数将pg放到空闲页面列表中,此时由于pg已经删除,则需要将其在父结点中的记录也删除掉,即转1,迭代删除;

7、对于删除记录后的pg,需要将已经删除的记录后面的记录进行前移;分为索引部分移动和数据部分移动;将biupper之间的记录向高地址方向移动bi大小的位置,这样就将删除bi留下的碎片交换到了空闲区域,从而形成一个整的空闲区域,注意,此时upper需要同时向高地址移动bi大小的位置;数据的移动同样到来索引值的调整,对于存放位置地址比bi高的记录,其索引值无须调整,对于存放位置地址比bi低的记录,需要将索引值加上bi的大小;同时,由于bi的删除,在索引区域留下了保存其索引的空间碎片,同样需要将其交换到空闲区域,即将bi后续的记录索引前移一位,将lower的值减1

8、将pg设为需要写回MPOOL_DIRTY;转9

9、如果h是跟结点,则将h置为需要写回MPOOL_DIRTY;返回成功;

10、使用__bt_relink函数将h从树中删除,同时使用__bt_free函数将h添加到空闲页面链表中;返回;

 

__bt_dleaf函数:从叶子页面中删除一条记录;

int __bt_dleaf(t, key, h, index)

     BTREE *t;

     const DBT *key;

     PAGE *h;

     u_int index;

参数列表:

t:树指针;

key:指定删除记录的键值;

h:待删除记录的页面;

index:待删除记录在页面中的索引;

返回值:成功、失败;

伪代码

首先判断游标是否指向待删除的记录,则需要调整游标;即如果t的游标已经初始化并且处于有效状态,指向记录的位置与待删除记录的位置相同,则使用__bt_curdel函数调整游标的位置;

获取待删除的记录blBLEAF);

如果bl使用了大数据,确保正确处理这些大数据;即

如果键值是大数据,使用__ovfl_delete函数删除键值;

如果数据值是大数据,使用__ovfl_delete函数删除数据值;

hindex之后的记录调整位置;

首先将数据区域中blupper之间的记录向高地址移动bl尺寸的位置,同时调整upper的值,完成bl数据存储区遗留空间的交换;

然后调整索引的值和位置;将index之后的索引位置前移一位;将数据存储地址比bl低的记录索引值增加bl尺寸的大小;

如果游标在h页面中,并且在index之后,则需要调整游标的指向,将其向前移动一条记录,保证刚好指向删除bl前游标指向的记录;

 

__bt_curdel函数:删除游标;

static int __bt_curdel(t, key, h, index)

     BTREE *t;

     const DBT *key;

     PAGE *h;

     u_int index;

参数列表:

t:树指针;

key:传入待删除记录的键值(DBT);

h:待删除记录所在页面;

index:待删除记录在h中的索引;

返回值:成功、失败;

伪代码

所谓删除游标就是将当前游标的值(键值)保存到游标的key成员变量中,同时将游标置无效,即如果树不支持复键,则将游标置CURS_ACQUIRE;如果游标支持复键,则需要将游标置为CURS_AFTERCURS_BEFORE,以记录向那个方向移动可以获取与当前待删除的游标键值相同的记录,首先判断于当前游标在同一页面的情况,如果当前游标的前一条记录的键值与当前游标的键值相同,则将游标置CURS_BEFORE,将游标指向前一条记录;如果上述比较失败,则比较相同页面内下一条记录的键值,如果相同,将游标置CURS_AFTER,将游标移动到下一条记录;如果游标处于当前页面的第一条或最后一条,且当且页面内前后的记录不符合上述要求,则可以在兄弟页面查找;具体操作如下:

获取t的游标c

去除cCURS_AFTER CURS_BEFORE CURS_ACQUIRE标记;

1、如果t不支持复键,转9

2、如果key为空,则后续的操作需要比较键值,所以使用__bt_ret函数读取当前键值,并将其赋给key

3、如果index大于0,即c指向的不是第一条记录,获取c的前一条记录e,比较e的键值与key的大小,若相等则将cCURS_BEFORE,转8;否则转4

4、如果index小于0,即c不为最后一条记录,获取c的下一条记录e,比较e的键值与key的大小,若相等则将cCURS_AFTER,转8;否则转5

5、如果index等于0,同时h的坐兄弟不为空,则获取h左兄弟最后一条记录e,比较比较e的键值与key的大小,若相等则将cCURS_BEFORE,转7;否则转6

6、如果ch最后一条记录且h的右兄弟不为空,则获取h右兄弟第一条记录e,比较e的键值与key的大小,若相等则将cCURS_AFTER,转7;否则去除e所在页面的PIN标记,然后转9

7、去除e所在页面的PIN标记;

8、将c指向e;返回成功;

9、使用__bt_ret将当前记录的键值读出存储到ckey成员变量中备份;将cCURS_ACQUIRE标记;(对于只有一条记录的复键,支持此种情况)

注意:CURS_ACQUIRECURS_AFTER CURS_BEFORE的区别在于: CURS_ACQUIRE表示删除游标操作后游标指向的记录是无效的;而CURS_AFTER CURS_BEFORE则表示删除游标操作后游标指向的记录是有效的,是与原游标键值相同的记录,根据后缀可以判断当前游标指向的记录与原游标的位置关系;

 

__bt_relink:从页面链表中删除某一个页面;

static int  __bt_relink(t, h)

     BTREE *t;

     PAGE *h;

参数列表:

t:树指针;

h:待删除的页面(PAGE);

返回值:0

伪代码

本函数完成双向链表删除结点操作;

如果h的右兄弟不为空,则获取h的右兄弟pg

pg的左兄弟置为h的左兄弟;

pg置为需要写回MPOOL_DIRTY

如果h的左兄弟不为空,则获取h的左兄弟pg

pg的右兄弟指向h的右兄弟;

pg置为需要写回MPOOL_DIRTY

 

 

 

阅读感悟:

总的来说还是没有完全理解BerkelyDB的一些意图,对于阅读学习来说,还是需要记录整理才能理解;最先开始读最新的4.8版,发现纷繁杂乱,无从着手,所以下了个最基础的版本1.8.6版。第一遍通读时自以为差不多理解了程序的意图,后来准备单个文件整理下,发现还是有很多疑问,作者的意图不能理解,随着整理的推进,一些疑惑得到解决,一些疑惑又产生,最典型的就是游标的CURS_ACQUIRECURS_AFTER CURS_BEFORE三个标记知道最后一个文件整理时才发现其用途。有必要在读几遍,然后在按照版本递进阅读到4.8版。

有一个问题一直没有搞明白,就是当大数据键值作为内节点的键值时,在删除叶子节点是不能删除这些值,但是如果内节点也删除了呢?在删除函数中未见到将大数据键值的持久存储标记去除的地方,这是否意味着即使树删除的只剩一个空的根结点,这些大数据仍然存在?

最后要说的是,其实最好的注释就是源代码!

 

原创粉丝点击