bt_conv.c 文件:用于字节顺序的转换,本机的字节顺序与硬盘上的机器独立格式的字节顺序;
__bt_pgin函数:从硬盘文件的字节顺序转化到本机的字节顺序;
参数列表:
t : B 树指针;
pg :待转换的页面编号;
h :待转换的页面;
返回值:无;
伪代码
如果 t 被设置为无须转换标志,则表示本机与硬盘上数据库文件使用的字节顺序相同,返回;
如果 pg 是元数据编号,则使用 mswap 函数为 pp 做转换,然后返回;
对于 pp 是一般树结点页面的情况,首先转化页面头部的页面基本信息,使用 M_32_SWAP宏和 M_16_SWAP宏完成对 PAGE结构中对应成员变量的转换;
接着转换页面的数据区域;
如果页面是 B树内节点页面:
遍历页面内记录,对于每条记录,首先使用 M_16_SWAP宏转换其索引,然后根据索引获取数据,根据 BINTERNAL结构,使用 P_32_SWAP宏转换前两项成员变量;如果数据存储的是大数据,则由于数据值实际存储的是页面编号和编号占用的内存大小,所以需要使用 P_32_SWAP宏转换;
如果页面时 B 树的叶子节点页面:
遍历所有记录,对于每条记录,首先使用 使用 M_16_SWAP宏转换其索引,然后根据索引获取数据,根据 BLEAF结构,使用 P_32_SWAP宏转换前两项成员变量;如果数据存储的是大数据,则需要使用 P_32_SWAP宏转换数据区中的信息;
__bt_pgout函数:从本机转换到磁盘格式;
参数列表:
t : B 树指针;
pg :待转换的页面编号;
h :待转换的页面;
返回值:无;
伪代码
如果 t 被设置为无须转换标志,则表示本机与硬盘上数据库文件使用的字节顺序相同,返回;
如果 pg 是元数据编号,则使用 mswap 函数为 pp 做转换,然后返回;
对于 pp 是一般树结点页面的情况:
如果是 B 树内部节点页面,则首先将页面中每条记录的数据部分转换,然后在转换索引,这点与 __bt_pgin函数相反,从而保证索引在定位数据时的有效性;
如果是 B树叶子节点页面,同样是首先将页面中每条记录的数据部分转换,然后转换其索引;
页面数据区转换完成后,转换页面头部的页面基本信息;
mswap函数:转换 B树元数据专用函数;
参数列表:
pg:页面指针;
返回值:无;
伪代码
根据 BTMETA的结构,依次对 magic、 version、 psize、 free、 nrecs、 flags成员做转换;
bt_get.c 文件:从 B 树中获取一条记录
__bt_get函数:从 B树中获取一条记录;
参数列表:
dbp:数据库指针;
key:待查找的键值;
data:返回查找结果的数据值;
flags:备用;
返回值:成功、失败、不确定(键值未找到);
伪代码
从 dbp中获取 B树指针 t;
去除 t的 PIN页面标记;
如果使用了备用的 flags标志,则返回错误;
使用 __bt_search函数查找键值为 key的记录 e,如果没有找到,则返回不确定;否则使用 __bt_ret函数获取 e的数据值存放到 data和 t的 bt_rdata中;
如果 t正被并行访问,则去除 e所在页面的 PIN标记,否则将 e所在页面赋给 t的 bt_pinned成员变量;
bt_open.c 文件:打开 B 树的文件;
__bt_open 函数:打开一个 B 树,主要是根据参数信息创建一个 DB 结构,同时调用相关函数打开一个 B 树;
参数列表:
fname :文件名,如果为空则为内存树;
flags :文件打开的标志;
mode :文件的打开模式;
dflags :全局标志,支持并发访问等;
openinfo : B 树的基本信息( BTREEINFO );
返回值:数据库指针;
伪代码
调用 byteorder 函数获取机器的字节顺序 machine_lorder ;
如果 openinfo 不为 NULL ,则将其赋值给 b ,检测其传入参数的合理性如下:
首先检测 BTREEINFO 结构中的 flags 成员是否设置了 R_DUP 之外的位,如果设置了,则返回失败;
检查 psize 的有效性,如果 psize = 0 ,则无效,如果 psize 大于 0 ,但是小于 MINPSIZE 或大于 MAX_PAGE_OFFSET + 1 ,或不为最小数据单元的整数倍(即 b.psize & sizeof(indx_t) - 1 ,其中 sizeof(indx_t) 是数据存储的基本单元,当前是 2 个字节,减 1 则将其二进制表示时低位置 1 ,与 b.psize 的&操作,检测其相应的低位是否为 0 ,从而判断是否被 sizeof(indx_t) 整除),则无效;返回失败;
检测 minkeypage 的有效性,如果 minkeypage<2 即每个页面可以存储小于 2 个键,无效,返回失败,如果 minkeypage 调用方没有设置,即传入了 0 值,表示采用缺省值 DEFMINKEYPAGE ;
检测 compare ,如果调用方没有设置,则使用缺省的比较函数 __bt_defcmp ,同时如果前缀比较函数也没有设置,则使用缺省的前缀比较函数 __bt_defpfx ;
检测 lorder ,如果 lorder 传入为 0 ,则使用机器的字节顺序 machine_lorder ;
以上检测了传入 openinfo 的有效性;
如果 openinfo 为 NULL ,则使用缺省值创建一个 BTREEINFO 结构 b ;
检测 b 的字节顺序是否合法,如果既不是大端有不是小端,则为非法,返回失败;
为树结构 t 和数据库结构 dbp 分配内存并初始化,需要注意的是如果树的字节顺序与机器的不同,则需要为 t 设置 B_NEEDSWAP (需要转换)标志;
如果 fname 不为空,则根据 flags 和 mode 打开树文件,同时将文件描述符赋给树的 bt_fd 成员变量保存,如果 flags 设置了 O_RDONLY ,还需将 t 设置为只读树;
如果 fname 为空,则为内存树,如果 flags 设置了可读写,则为非法标志,返回失败;使用 tmp ()函数获取一个临时文件描述符赋给树的 bt_fd 成员变量,同时设置 t 为内存树;
根据 t->bt_fd 获取文件的状态信息,根据其中文件大小的信息来判断是新建树,还是打开已有的树:
如果文件大小不为 0 ,则读取文件前 sizeof(BTMETA) 个数据,保存到 m 中,由树文件的布局可知,这是读取了树的元数据;需要根据元数据信息重新更正树的一些基本信息,如下:
如果 m 的 magic 变量等于 BTREEMAGIC ,则清除 t 的 B_NEEDSWAP ,这是因为 m 是直接从文件中使用字节的方式读出,如果上述比较相等,则表明文件中存储的字节顺序和机器是相同的,所以即使传入的参数可能与文件的实际情况冲突,但是仍然应该以文件实际的存储情况为准;如果不相等,则需要为 t 设置 B_NEEDSWAP ,同时表明当前 m 的字节顺序不能得到有效的值,需要使用 M_32_SWAP 等宏将其成员变量转换成与机器相同的字节顺序。
经过上述处理后,如果 m 的 magic 仍然与 BTREEMAGIC 不相等,或 m 的 version 不等于当前程序的版本号 BTREEVERSION ,则返回失败;
检测 m 的 psize 的有效性;
检测 m 的 flags 成员变量的有效性;
根据 m 的 psize 调整 b 的 psize ;
为 t 设置 m 的 flags 标志;
将 m 中保存的树信息传递到 t 的相关成员变量,主要是空闲页面链表和记录数量;
以上是在文件不为空文件时检查树的元数据有效性,并据此调整树的有关基本信息;
如果文件大小为 0 ,表示是新建树,此时需要设定一些信息,如下:
如果 b 的 psize 即当前树的页面大小为 0 ,则采用文件的 st_blksize 作为页面大小,这样可以获得较高的 I/O 效率,如果 st_blksize 超出了 MINPSIZE 和 MAX_PAGE_OFFSET + 1 设定的边界值,则 psize 取二者中与 st_blksize 较接近的那一个;
如果 b 的 flags 不支持复键,则设置 t 不支持复键;
设置 t 的空闲页面链表为空,记录数量为 0 ;
设置 t 的元数据需要更新标记;
这样处理完成 t 与文件之间的关系;
由于上述操作中可能会调整 t 的页面大小,将 b 的 psize 内容更新到 t 的 bt_psize 成员变量;
计算缓冲池的大小如下:
缓冲池的尺寸必须为树页面的整数倍(因为是按页面缓冲),对于 b 的 cachesize 成员变量,如果已经设置且不是 cachesize 的整数倍,则补齐到页面的整数倍,即 b.cachesize += (~b.cachesize & b.psize - 1) + 1 表达式;
如果 b 的 cachesize 成员变量小于 b.psize * MINCACHE (缓冲区最小尺寸),则 b.cachesize 赋值为缓冲区的最小值;
计算缓冲区所占的页面数 ncache ;
计算大数据的标准:
由于每个页面最少必须具备指定数量的记录, berkely DB 将这一限制转换成页面中平均每条记录(键 / 数据对)能够使用的尺寸(字节数),超出这一字数将作为大数据处理,将存放到溢出页面上;在计算时需要考虑到页面头部的页面基本信息尺寸、叶子节点的索引和节点本身的尺寸;即 t->bt_ovflsize = (t->bt_psize - BTDATAOFF) / b.minkeypage - (sizeof(indx_t) + NBLEAFDBT(0, 0)); 其中 (t->bt_psize - BTDATAOFF) 得到数据区的大小,除以 b.minkeypage 得到每条记录最大能够占用的空间,记录存储占用的空间包括索引和记录本身的信息,其中记录使用 BLEAF 结构存储,实际的数据还需要减去 BLEAF 结构中的辅助信息,其大小为 NBLEAFDBT(0, 0) ,索引的大小为 sizeof(indx_t) ,从而得到大数据的标准;
完成上述计算后,需要注意,为了防止由于用户设定的 minkeypage 过大,从而导致即使数据存放到溢出页面上,剩余的空间仍然不够存放索引也出页面的编号的现象出现,需要验证计算的大数据标准是否过小,即大数据的标准不能小于 NBLEAFDBT(NOVFLSIZE, NOVFLSIZE) + sizeof(indx_t) ,这一表达式是对大数据存储时使用的最小记录尺寸,其中 sizeof(indx_t) 是索引的尺寸, NOVFLSIZE 是页面标号及编号所占内存大小的和,由于记录由键值和数据组成,故 NBLEAFDBT(NOVFLSIZE, NOVFLSIZE) 计算出了存储一条大数据记录时占用的最小空间;
根据 bt_fd 、 bt_psize 、 ncache ,使用 mpool_open 函数初始化缓冲池;
如果是一新创建的树,使用 nroot 函数创建一个根结点页面;
根据 dflags 设置 t 的全局标志,如支持锁等;
返回 dbp ;
nroot 函数:为一个新树创建根页面;
参数列表:
t :树指针;
返回值:成功、失败;
伪代码
使用 mpool_get 函数获取 t 的第一页,即根结点页面 root ;
如果 root 存在,检测其有效性;返回;
如果 root 不存在,表示这是一个新建的树,其元数据页和根结点页都需要创建和初始化:
首先获取一个新的页面 meta ,再获取一个新的页面 root ,编号设定的方式都是递增;
如果获取 root 时得到的页面编号不为 P_ROOT ,则表示出现错误,返回失败;
设置 root 的页面编号,左右兄弟结点为空(此时树只有一个根结点,故为空),初始化空闲数据区指针,设置页面为叶子页面;
将 meta 页面初始化为 0 ,将 meta 页面和 root 页面置为 MPOOL_DIRTY ;
tmp 函数:创建临时文件夹;
参数列表:无
返回值:文件描述符;
伪代码
获取临时文件夹路径 envtmp ;
如果 envtmp 存在,则临时文件路径为 /$envtmp/bt.XXXXXX ;否则,为 /tmp/bt.XXXXXX ;
创建临时文件;
byteorder 函数:获取机器的字节顺序;
参数列表:无
返回值: BIG_ENDIAN (大端), LITTLE_ENDIAN (小端);
伪代码
将 x 初始化为 0x01020304 ;
如果 x 的第一个字节为 1 ,则为 BIG_ENDIAN ;为 4 则为 LITTLE_ENDIAN ;否则操作失败;
__bt_fd 函数:获取数据库文件的描述符;
参数列表:
dbp :数据库指针;
返回值:文件描述符;
伪代码
从 dbp 中获取树指针 t ;
去除 t 中 PIN 页面标记;
如果是内存树,返回- 1 ;
否则返回 t 的 bt_fd 成员变量;
bt_split.c 文件:用于树的分裂操作;
__bt_split 函数:完成向树中插入记录,并分裂相关结点的操作;
参数列表:
t :树指针;
sp :待分裂的结点页面;
key :待插入记录的键值;
data :待插入记录的数据值;
flags :记录是否是大数据;
ilen :插入记录的尺寸;
argskip :插入的位置;
返回值:成功、失败;
伪代码
由于在插入之前需要将 sp 分裂成左右两个结点页面,如果插入点位于右子结点,其插入的位置需要调整,将 argskip 赋给局部变量 skip ,便于后续的操作,实际插入的位置将有 skip 传递;
如果 sp 是根结点,使用 bt_root 函数分裂 sp ,否则使用 bt_page 函数分裂 sp ;分裂的结果是得到两个页面结点 l 和 r ,返回实际插入的页面 h ( l 或 r )及修订实际插入的位置 skip ;
将 key 和 data 插入到 h 的 skip 处的操作:
调整 h 的 upper 成员变量为记录分配 ilen 大小的内存,并将首地址相对值传递给记录索引的 skip 处;
根据首地址相对计算出实际地址 dest ;
使用对应的宏完成将 key 和 data 写入到 dest 的操作( B 树可以为 B 树算法和 RECNO 算法公用,二者实际用于写入的宏有所不同);
完成插入后,需要注意的是:结点的分裂,对于根结点实际上创建两个新的结点页面来存储根结点中的数据,然后再将这两个结点页面的编号存放到根结点中;对于普通的结点,则是创建一个新的页面来存储部分记录,然后将这个结点页面插入到 B 树中;
如果 sp 是根结点,则还需要使用 bt_rroot 函数( RECNO 算法)或 bt_broot 函数来重新调整根结点;
对于 sp 不是根结点的情况,沿着祖先结点栈向上回溯,将新生成的结点插入的父结点中,如果父结点也需要分裂才能接纳新的结点,则递归完成这一插入过程,知道到达根结点或不再分裂为止; 内结点记录中的关键字实际上是对应子结点的第一条记录的键值;
1 、弹出父结点记录 parent ( EPGNO );
2 、将 l 赋给 lchild , r 赋给 rchild ,并使用 mpool_get 获取 parent 的页面 h ;
3 、将插入的位置 skip 设为 parent->index + 1 ,即原结点 sp 所在位置的下一位,因为分裂后左结点 l 继承了分裂前结点的位置,新插入的结点右结点 r 是其右兄弟;
4 、计算新结点在父结点中需要占用的空间;
如果插入结点是 B 树内结点,则使用 GETBINTERNAL 宏获取新结点的第一条记录的数据结构 bi ,再使用 NBINTERNAL 宏计算存储 bi 的键值需要的内存大小 nbytes ;
如果插入结点是 B 树叶子结点,则使用 GETBLEAF 宏获取新结点的第一条记录的数据结构 bl ,再使用 NBINTERNAL 宏计算存储 bl 的键值需要的内存大小 nbytes ;另外需要考虑前缀树的情况(不知道是干啥用的?),就是在 t 的 bt_pfx 函数不为空的情况下, bl 存储的不是大数据,且 h 的坐兄弟结点不为空或插入位置大于 1 ,使用 bt_pfx 函数获取 lchild 的最后一条记录的键值和 bl 第一条记录键值的相同前缀的尺寸 nksize ,使用 NBINTERNAL 宏计算当键值长度为 nksize 时,存储需要的内存 n ,如果 n<nbytes, 则 nbytes = n ,即将会截断实际存储的键值,否则 nksize = 0 ;推测这样可能会加快查询的速度,因为如此操作缩短了键值的长度,从而加快了比较的速度;
如果是 RECNO 算法结点,则使用 NRINTERNAL 宏计算 nbytes ;
5 、检测是否需要分裂 parent 所在的页面并调整插入点以用于插入新结点;
如果需要分裂父结点,则使用 bt_root 或 bt_page 函数分裂父结点(同本函数开始时的情况),得到待插入结点的新的父结点页面 h ;将 parentsplit 置 1 ;
如果不需要分裂,则需要将 h 中插入点以后的记录索引向后移动一位,为插入点的索引腾出空间,同时调整 h 的 lower 指针; parensplit 置 0 ;
6 、将新结点插入到父结点中:
如果 rchild 为 B 树内结点,首先为其分配空间,即调整 h 的 upper 和对应的索引值;
然后将 bi 的键值部分复制的新分配的位置,最后保存 rchild 的页面编号;
如果 rchild 是 B 树叶子结点,同样是分配空间,然后使用 WR_BINTERNAL 宏将记录的结构部分填充,接着将 bl 的键值复制到对应内存,最后判断如果 bl 存储的是大数据,则需要使用 bt_preserve 函数将这一大数据标记为不可删除的,以免后续对记录的删除同时删除了索引用的键值,从而造成脏数据;
如果 rchild 是 RECNO 算法的结点,暂无;
7 、如果 parentsplit 为 0 ,即无须继续插入操作,则将 h 置为需要写回 MPOOL_DIRTY ,结束分裂操作;
8 、对于 parentsplit 为 1 的情形,需要将分裂的父结点插入的 B 树中,这样需要判断当前分裂的父结点是否是根结点,若是,则需要使用 bt_rroot 函数或 bt_broot 函数来调整新的根结点;
9 、将 lchild 、 rchild 结点页面置为需要写回 MPOOL_DIRTY ,同时去除 PIN 标记,转 1 ;
结束分裂操作,将 l 、 r 结点页面置为需要写回 MPOOL_DIRTY ,同时去除 PIN 标记,返回成功;
bt_page 函数:分裂一个非根结点;
参数列表:
t :树指针;
h :待分裂的页面;
lp :返回分裂产生的左页面;
rp :返回分裂产生的右页面;
skip :传入和返回插入点位置;
ilen :待插入数据的长度;
返回值:分裂后待插入的页面;
伪代码
使用 __bt_new 函数获取一个页面 r ,用于存储分裂的数据;
初始化 r ,其中 t 的右兄弟为 h 的右兄弟,左兄弟为 h ,页面类型与 h 相同;
如果插入的是 B 树当前级的最右边结点,则只用插入一个空的页面即可,这样即使下次需要插入并分裂时,也是可以的在作的;即 h 的右兄弟结点为空,且 *skip == NEXTINDEX(h) 时,将 h 的右兄弟设为 r , r 的 lower 指针下移一位,为插入的结点分配空间,同时插入点 skip 调整成 0 , lp 指向 h , rp 指向 r ,返回 r ;
为新的左页面分配存(其实是临时使用的内存) l ;
为 l 初始化,并将其左兄弟设为 h 的左兄弟,右兄弟设为 r ,类型与 h 相同;
由于 r 将会插入到 B 树中 h 的后面,所以需要调整 h 的右兄弟的左兄弟,使其指向 r ;
使用 bt_psplit 函数完成实际的分裂工作,即将 h 中的数据复制到 l 和 r 中,同时为新结点分配索引的空间;同时返回实际需要插入的页面 tp 和位置 skip ;
将 l 的内容复制到 h 中,如果 tp == l 则, tp 改为 h ;释放 l 的所占的内存空间;
将 lp 设定为 h ; rp 设定为 r ,返回 tp ;
bt_root 函数:分裂 B 树的根结点;
参数列表:
t :树指针;
h :根结点页面;
lp :指向分裂的左页面;
rp :指向分裂的右页面;
skip :插入点;
ilen :插入记录的长度;
返回值:返回指向待插入记录的页面;
伪代码
使用 __bt_new 函数分别为左页面和右页面获取新的页面 l 和 r ;
初始化 l 和 r 页面;其中 l 的右兄弟为 r , r 的左兄弟为 l , l 的左兄弟和 r 的右兄弟为空, l 和 r 的页面类型与 h 相同;
使用 bt_psplit 函数完成实际的分裂工作,即将 h 中的数据复制到 l 和 r 中,同时为新结点分配索引的空间;同时返回实际需要插入的页面 tp 和位置 skip ;
将 lp 设定为 h ; rp 设定为 r ,返回 tp ;
bt_rroot 函数:
bt_broot 函数:在根结点分裂后,调整 B 树的根结点;
t :树指针;
h :根结点页面;
l :左子结点页面;
r :右子结点页面;
返回值:成功、失败;
伪代码
将左结点页面 l 在 h 中注册,由于 l 是根结点最左边的记录,其关键字在树的搜索中永远不会用到,所以不用填写,这样,存放 l 结点相关信息就只剩下存储 l 的页面编号了,即使用 NBINTERNAL(0) 宏获取需要的内存大小 nbytes ,然后在 h 中为其分配内存,最后使用 WR_BINTERNAL 宏完成写入;
如果 h 是叶子结点,则经过分裂后,会变成内部结点;
存储 r 结点的信息:
如果 h 是叶子结点,使用 GETBLEAF 宏获取 r 第一条记录的信息 bl ( BLEAF );使用宏 NBINTERNAL 计算以 bl 的键值为键值的内结点需要的内存 nbytes ;在 h 中为存储 r 的信息分配内存,使用 WR_BINTERNAL 宏写入指向 r 的记录,并将 bl 的键值复制到对应的位置;同样,如果 bl 存储的是大数据,需要使用 bt_preserve 函数将其标记成永久保存的标志;
如果 h 是内部结点,则同样获取 r 的第一条记录 bi ( BINTERNAL ),计算以 bi 键值为键值的内结点记录需要的内存大小 nbytes ,分配内存,复制键值信息和填写 r 的页面编号信息;
由于根结点中已经有两条记录,所以调整 h 的 lower 变量;
清除 h 的页面类型;
将 h 设置为 B 树内结点页面类型;
将 h 设置为需要写回标记 MPOOL_DIRTY ;
bt_psplit 函数:为页面分裂作实际的数据转移操作;
参数列表:
t :树指针;
h :待分裂的页面;
l :分裂后的左页面;
r :分裂后的右页面;
pskip :传入和返回记录插入的位置;
ilen :插入记录的尺寸;
返回值:返回待插入的页面;
伪代码
1 、首先完成对 l 页面的数据转储,转储的规则是尽量不要上大数据成为页面的键值,因为这会导致内结点处理效率的低下(需要先寻址,再比较),而且当对应的叶子结点删除时,大数据不能删除,从而降低了空间利用率;具体措施如下:
遍历 h 页面,直到符合适当的条件就停止遍历,在这之前的内结点记录全部转储到 l 页面;对于不是插入点位置的记录,根据记录的类型使用对应的函数获取记录的首地址 src 和尺寸 nbytes ,并使用 isbigkey 来标记是否是大数据;
如果遍历的插入点位置,则将 nbytes 赋值为 ilen ;
使用 used 保存已经转储的字节数;
如果如果已经到达或超过插入点,且计入当前记录后已经转储的字节数( used + nbytes )将超出页面存储数据的最大能力,或者已经遍历到 h 最后一条记录,这时,停止遍历,不再想 l 转储数据,保存已经转储的最后一条记录的编号 off ;
如果可以上述条件不满足,则表示当前记录可以转储到 l 结点中,如果当前位置不是插入点,则将当前记录转储到 l 中;并将 used 增加 nbytes ;
此时,如果已经转储的字节数 used 超过了页面存储能力的一半,则需要作一些判度,以决定是否继续转储:如果当前记录存储的不是大数据,或已经转储了 3 条存储大数据的记录,则停止转储,否则将已经转储的大数据记录个数加 1 ,继续转储;
完成对左结点页面的转储后,调整左结点的 lower 指针(在转储是为了效率,只是调整了 upper 的指针, lower 指针可以通过计算一次性调整);
由于插入点可能在 h 中,插入数据和分裂结点后需要调整游标的位置;如果游标已经初始化并且指向 h 中的记录,则调整如下:
如果游标指向的位置在 skip 之后,即大于 skip ,则游标的位置需要后移动一位,以便继续指向以前的记录;
如果经过上述调整后游标的位置小于 l 结点内记录的数量,表示游标指向的记录分配在 l 结点中,将游标的页面指向调整到 l 页面;否则,则需要将游标的页面指向 r ,并且游标指向的位置需要减去 l 结点存储结点的数量;
如果插入点在 l 中,则插入点的位置不需调整,返回的页面为 l ;否则,返回的页面为 r ,插入点的位置需要调整,即减去已经转储到 l 的记录数;
2 、转储记录到右结点 r ;
同转储到 l 一样;如果是插入点,则为其预留位置;如果不是插入点,根据 h 的类型,获取相关键值和存储需要的内存大小 nbytes ;由于剩下的记录将全部转储到 r 中,所以无须作过多的判断,直接将数据转储到 r 中的对应位置即可;
最后调整 r 结点的 lower 指针;
需要注意的是,如果插入点其实是在 h 的最后一条记录之后,则上述的遍历将不能找到插入点,这时需要将插入点追加到 r 的后面,即将 r 的 lower 指针加 1 ,为插入点预留索引的存储空间;
返回待插入的页面;
bt_preserve 函数:对于作为内结点记录键值的大数据,需要将其设置成永久性保存;
参数列表:
t :树指针;
pg :待设置的页面编号;
返回值:成功、失败;
伪代码
获取页面编号为 pg 的页面 h ;
将 h 设置成 P_PRESERVE ;
将 h 标记成 MPOOL_DIRTY ;
rec_total 函数: