B+树是 B-树的变体,也是一颗多路搜索树。一颗 m 阶的 B+树具有以下特点:
1.每个结点最多有 m 个孩子。
2. 非根节点关键值个数范围:
⌈
m/2
⌉
- 1 <= k <= m-1
3.相邻叶子结点通过指针联系起来形成一个链表
例如下图是一颗 3 阶的 B+树:
B+树和 B 树的区别
•
B 树内部节点是保存数据的;而 B+树内部节点是不保存数据的,只作索引作用,它的叶子节点才保存数据。
•
B+树相邻的叶子节点之间是通过链表指针连起来的,B 树却不是。
•
查找过程中,B 树在找到具体的数值以后就结束,而 B+树则需要通过索引找到叶子结点中的数据才结束
•
B 树中任何一个关键字出现且只出现在一个结点中,而 B+树可以出现多次。
B+树的插入操作
B+树插入为以下几个步骤:
1.B+树插入都是在叶子结点进行的,就是插入前,需要先找到要插入的叶子结点。
2.如果被插入关键字的叶子节点,当前含有的关键字数量是小于阶数 m,则直接插入。
3.如果插入关键字后,叶子节点当前含有的关键字数目等于阶数 m,则插入后该节点开始分裂为两个新的节点,一个节点包含⌊
m/2
⌋
个关键字,另外一个关键字包含
⌈
m/2
⌉个关键值。(⌊
m/2
⌋
表示向下取整,
⌈
m/2
⌉
表示向上取整,如
⌈
3/2
⌉
=2)。
4.分裂后,需要将第
⌈
m/2
⌉
的关键字上移到父结点。如果这时候父结点中包含的关键字个数小于 m,则插入操作完成。如果父结点中包含的关键字个数等于 m,则继续分裂父结点。以一棵 4 阶的 B+树为例,4 阶 B+树关键值最多为 4-1 个,假如待插入的数据为 43,48,36,32,37,49,28,则有以下步骤。
第一步:
在空树上插入 43
第二步:依次插入 48,36 到结点
第三步:
这个时候此结点满了,继续插入 32,引起结点分裂,于是分裂 第
⌈
4/2
⌉
=2(下标 0,1,2)个,也即 43 上移到父节点。
第四步:继续插入 37,49
第五步:
最后插入 28,发现当前节点关键字也是不小于阶数 4 了,于是分裂,于是分裂,第 ⌈
4/2
⌉
=2 个,也就是 36 上移到父节点,因父子节点只有 2 个关键值,还是小于 4 的,所以不用继续分裂,插入完成
B+树的删除操作
B+树删除关键字,分这几种情况
•
找到包含关键值的结点,如果关键字个数大于
⌈
m/2
⌉
-1,直接删除即可;
•
找到包含关键值的结点,如果关键字个数大于
⌈
m/2
⌉
-1,并且关键值是当前节点的最大(小)值,并且该关键值存在父子节点中,那么删除该关键字,同时需要相应调整父节点的值。
•
找到包含关键值的结点,如果删除该关键字后,关键字个数小于
⌈
m/2
⌉
,并且其兄弟结点有多余的关键字,则从其兄弟结点借用关键字
•
找到包含关键值的结点,如果删除该关键字后,关键字个数小于
⌈
m/2
⌉
,并且其兄弟结点没有多余的关键字,则与兄弟结点合并。
示例:
第一种情况:假设当前有这么一颗 5 阶的 B+树
要删除 22,因为该结点关键字个数 3>⌈5/2⌉-1,所以直接删除即可
第二种情况:如下图,要删除元素 20
因为 20 是该结点关键字的最小值,删除该元素要调整双亲结点的关键字
第三种情况:如下图,删除元素 15
如果删除 15,删除关键字的结点只剩 1 个关键字,小于⌈
5/2
⌉
-1=2,不满足 B+树特点,但是其兄弟节点拥有 3 个元素(7,8,9),可以借用 9 过来,如图:
第四种情况:如下图,删除关键字 7
因为 7 被删掉后,只剩一个 8 的关键字,不满足 B+树特点(⌈
m/2
⌉
-1<=关键字<=m-1)。并且没有兄弟结点关键字借用,因此 8 与前面的兄弟结点结合。被删关键字结点的父节点,7 索引也被删掉了,只剩一个 9,并且其右兄弟结点(18,20)只有两个关键字,也是没得借,因此在此合并。被删关键字结点的父子节点,也和其兄弟结点合并后,只剩一个子树分支,因此根节点(16)也下移了。
B+树的面试问题
1. InnoDB 一棵 B+树可以存放多少行数据?
答:约两千万行,因为计算机中,磁盘的存储数据最小单元是扇区,一个扇区是521 个字节,而文件系统中,最小单元是块,一个块的大小就是 4k,但是 InnoDB存储引擎最小存储单元是页,一页大小就是 16k
因为 B+树叶子结点存的是数据,内部结点存的是键值和指针,索引组织表通过非叶子结点的二分查找法以及指针确定数据在哪个页中,进而再去数据页中找到需要的数据。假设 B+树的高度为 2 的话,即有一个根结点和若干个叶子结点,那么这颗 B_树的存放总记录数为根节点指针数*单个叶子节点记录行数,如果一行记录的数据大小为 1k,那么单个叶子节点可以存放的记录数为 16k/1k=16,我们假设主键 ID 为 bigint 类型,长度为 8 字节,而指针大小在 InnoDB 源码中设置为6 字节,所以就是 8+6=14 字节,16k/14B =16*1024B/14B = 1170,因此,一颗高度为 2 的 B+数,能存放 1170*16=18720 条这样的数据记录。同理一棵高度为 3的 B+树,能存放 1170 *1170 *16 =21902400,也就是说,可以存放两千万左右的记录。B+树高度一般为 1-3 层,已经满足千万级别的数据存储。
2. 为什么索引结构默认使用 B+树,而不是 hash,二叉树,红黑树,B 树?
Hash 哈希,只适合等值查询,不适合范围查询。一般二叉树,可能会特殊化为一个链表,相当于全表扫描。红黑树,是一种特化的平衡二叉树,MySQL 数据量很大的时候,索引的体积也会很大,内存放不下的而从磁盘读取,树的层次太高的话,读取磁盘的次数就多了。B 数的叶子节点和非叶子节点都保存数据,相同的数据量,B+树更矮壮,也是就说,相同的数据量,B+树数据结构,查询磁盘的次数会更少。