前言
B+树广泛应用在文件系统和数据库系统如windows的HPFS文件系统,linux的JFS文件系统还有数据库MYSQL的 InnoDB索引等。掌握好B+树基础对如何正确使用MYSQL索引和性能优化有很大帮助。前面写了篇对B树理解总结的文章,现在对B+树继续学习理解。
一、B+树是什么?
首先B+树是在B树基础上改进,在B+树里面非叶子结点不存储数据,仅仅是作为索引,只有叶子结点是存储数据。并且有一个指针指向最小关键字的叶子结点,所有的叶子结点连接成一个双向链表。B+树所有关键字均会出现在叶子结点,并且从左到右按照从小到大顺序分布。各层结点中的关键字是其子结点中最大或者最小关键字的复写。
查看了许多资料对B+树各有不同定义,这里我采用维基百科上所定义的方式。假设一颗m阶B+树它满足以下性质:
1.根结点的子结点至少有两个,除非当叶子结点就是根结点,此时B+树只有一个根结点
2.每个结点至多有m个子结点
3.除了根结点和叶子结点外,,其它每个结点至少有
⌈
m
2
⌉
\lceil \frac{m}{2}\rceil
⌈2m⌉个子结点
4.所有叶子结点都在同一层
5.每个非根结点包含关键字个数k满足:
⌈
m
2
⌉
−
1
≤
k
≤
m
−
1
\lceil \frac{m}{2}\rceil-1 \leq k \leq m-1
⌈2m⌉−1≤k≤m−1
6.根结点包含关键字个数k满足:
1
≤
k
≤
m
−
1
1 \leq k \leq m-1
1≤k≤m−1
7.所有的叶子结点连接成一个双向链表,叶子结点本身关键字从小到大顺序排序。
如下图显示的是5阶B+树
B+树查询
查询单个关键字
查询单个关键字,B+树会从根结点向下逐层查找,最终找到匹配的叶子结点。我们以查找关键字13为例说明。
第一次磁盘 I/O :访问根结点 [7] ,把数据放进内存进行对比,发现13大于 7 ,则访问根结点的右孩子结点。
第二次磁盘 I/O : 访问结点 [9、11、13] ,把数据放进内存进行对比,发现 13 大于等于 13 ,于是访问当前结点的第四个孩子结点 [13、14、15] 。
第三次磁盘 I/O :访问叶子结点 [13、14、15] ,从左到右顺序遍历结点内部关键字,找到要查找的13查询结束。
从上面查询过程中我们可以知道,在B+树里只有叶子结点才存放数据,非叶子结点只作为索引,因此即使在中间结点查询匹配到关键字,也要一直遍历到叶子结点。 所以在B+树中对于查找任何一个关键字都要从根结点开始遍历到叶子结点才结束。
关键字区间查询
在B+树里进行范围查询是非常有优势,我们以查找大于等于12,小于等于14的关键字为例说明。
首先B+树会从根结点向下逐层查找,最终找到匹配12关键字的叶子结点,因所有的叶子结点连接成一个双向链表,因此只需要从左端12关键字一直顺序遍历到14关键字即可,中间没有任何磁盘IO操作。
B+树插入
在B+树中插入关键字操作,一般有以下 三种情况
1.如果插入结点的关键字数量少于m-1,则直接插入。如下图5阶B+树插入6关键字为例说明。
因[3、4、5]叶子结点的关键字数为3少于4,因此直接插入6,插入后如下图所示
2.如果插入结点的关键字数量等于m-1,插入后会引起分裂。假设结点关键字的中间位置为k,则会将K位置的关键字向上父节点合并;(0,k-1)和(k+1,m)位置的关键字会分裂成两个子结点。一次分裂后有可能会导致父结点上溢,因此也会导致父节点分裂,最极端的情况下有可能会一直分裂到父结点。
如下图5阶B+树插入7关键字为例说明
插入7关键字后如下图所示,由于右孩子结点关键字数等于5,超出限制数为4,因此要进行分裂,叶子结点分裂时,分裂出来[3、4]为左孩子结点,[5、6、7]为右孩子结点,中间关键字5向上父结点合并,当前结点指针指向父结点。
最后插入结果如下图所示,由于父结点[3、5]关键字数没有超出限制,因此父结点不用分裂,插入结束。
3.如果插入叶子结点满了会向上合并,如果合并的父结点也满了,会一直分裂,可能会造成大量分裂,大量分裂意味着需要频繁访问磁盘 I/O。为了尽量减少分裂合并,B+树提供了旋转功能。当插入叶子结点满的情况下,在其左右兄弟结点没有满的情况下,这时B+树不会急着分裂,通常会检查左邻兄弟如果没有满,则会发生右旋转。
B+树删除
B+树删除比较复杂,通常有以下几种情况以如下5阶B+树为例说明
1.如果被删除的叶子结点关键字数大于
⌈
m
2
⌉
−
1
\lceil \frac{m}{2}\rceil-1
⌈2m⌉−1,那么直接删除即可。
比如删除90,因结点[75、80、85、90]的关键数大于2,因此直接删除。
2.如果被删除的叶子结点关键字数等于
⌈
m
2
⌉
−
1
\lceil \frac{m}{2}\rceil-1
⌈2m⌉−1,其兄弟结点的关键数大于
⌈
m
2
⌉
−
1
\lceil \frac{m}{2}\rceil-1
⌈2m⌉−1,删除后向其兄弟借一个。
比如删除60,因结点[60、65]关键数等于2,删除60后向其兄弟借55,此删除操作称为右旋转。删除后如下图示例。
2.如果被删除的叶子结点关键字数等于
⌈
m
2
⌉
−
1
\lceil \frac{m}{2}\rceil-1
⌈2m⌉−1,其兄弟结点关键字也恰好等于
⌈
m
2
⌉
−
1
\lceil \frac{m}{2}\rceil-1
⌈2m⌉−1,可以将它父节点关键字挪下来与左右结点关键字进行合并,这种情况会导致父结点下溢,最极端的情况下溢现象可能会一直往上传播,直到根结点。
比如删除15,因结点[15、20]关键字数等于2,其兄弟结点[5、10]关键字数也恰好等于2,删除后需要将它父节点关键字挪下来与左右结点关键字进行合并。合并后如下图示例。
因合并后结点[25]关键数少于2,因此需要在次将它父节点关键字挪下来与左右结点关键字进行合并。合并后如下图所示。
2.如果被删除的叶子结点关键字也在父结点,则需要使用被删除的叶子结点最小关键字或者最大关键字替换父结点的相同关键字。
比如删除50,因根结点[50]也存在该关键字,因此删除后需要将叶子结点[51、52]的最小关键字51替换,替换后如下图所示。
二、B+树和B树的区别
1.在B+树中,叶子结点包含数据,而非叶结点仅起到索引作用。非叶结点中的每个索引项只含对应的子树的最大或者最小关键字和指向该子树的指针,不含有该关键字对应记录的存储地址,而在B树中每个关键字对应一个记录的存储地址。
2.在B+树中,叶子结点包含全部关键字,在非叶结点出现的关键字也会出现在叶结点中。而在B树中,叶子结点包含的关键字和其他结点包含的关键字是不重复的。
3.所有的叶子结点连接成一个双向链表,叶子结点本身关键字从小到大顺序排序。
总结
本篇文章是对B+树学习的一些总结,主要讲述了B+树是在B树基础上扩展得来因此一些特性基本和B树相同,也讲解了B+树的一些基础操作比如新增关键字,在B+树里整体是如何变化的。