1、B树简绍
B 树又称平衡多路查找树。
1.1、用阶来描述 B 树
一个 m 阶的 B 树具有一下特点:
1 每个结点最多包含 m 个子节点;
2 除根节点外,每个分支结点至少有 ceil(m/2) 个子节点;
3 根结点若非叶子结点,则至少 2 个子节点;
4 所有叶子结点都出现在同一层;
5 有 n 个子结点的非叶子结点恰好有 n - 1 个关键字,关键字按递增顺序排序。
1.2、用图片描述 B 树
下图是一个 4 阶的 B 树
下图是一个 3 阶的 B 树
1.3、用代码描述 B 树
#define m 1024
struct BTNode;
typedef struct BTNode * PBTNode;
struct BTNode {
int keyNum; // 实际关键字个数,key < m
PBTNode parent; // 指向父节点
PBTNode *ptr; // 子树指向向量:ptr[0]...ptr[keyNum]
KeyType *key; // 关键字向量:key[0]...key[keyNum-1]
};
typedef struct BTNode * BTree;
typedef struct BTree * PBTree
2、B+ 树介绍
B+ 树是 B 树的变形。一棵 m 阶 B+ 树和 m 阶 B 树的异同点如下:
1 有 n 个子结点的 B+ 树有 n 个关键字,而 B 树则是有 n -1 个关键字;
2 B+ 树的内结点不存储 data ,只存储 key;
3 B+ 树的所有叶子结点中包含所有信息,叶子结点本身依关键字大小依次顺序链接;
4 B+ 树所有的非终端结点可以看成是索引部分,结点中仅含有其子结点的最大(或最小)关键字。
2.1、用图片描述 B+ 树
下图是一个三阶的 B+ 树
3、B+树查找过程及其效率
上图中,假设需要查询 key 为 80 的数据,首先需要将根结点加载,通过比较,80 大于 65,则会通过 P3 指针再加载对应的结点,在将结点上的 key 与 80 比较,最后定位在 P2 指针对应的区域在进行加载获取数据。
上述查找过程,一共进行了 3 次加载过程,加载次数本质上 B+ 树的树高。实际上,B+ 树的查找效率取决于树高 h。
假设当前数据表的数据量为 N,每个结点的关键字数量是 m,则树高 :
在根据对数函数的函数特性:
当数据量 N 情况一定的情况下,m 越大,h 越小。数据库系统的设计者巧妙利用了磁盘预读原理,将一个节点的大小设为等于一个页,这样每个节点只需要一次I/O就可以完全载入。而 m = 磁盘块大小/数据项大小,磁盘块的大小就是数据页的大小,是固定的,一般是 4k。
从上可以看出,要提高 B+ 树的查询效率,就需要降低树高,要降低树高,数据库系统就要求数据项的大小尽量小。Mysql 就采用 B+ 树作为索引文件,索引字段的大小与数据项的大小成正比,因此在数据库查询调优中,就有要求索引字段尽量小。短小的索引字段,能够有效的降低树高,提高查询效率。
4、B+ 树结点数据的插入
B+ 树大致的插入算法如下:
1)寻找需要插入数据的目标叶节点;
2)判断插入的数据是否比 B+ 树目前最小值还小,如果是更新全树非叶结点的最小值;
3)判断目标叶结点是否数据满,未满的情况下直接插入数据;
4)在叶结点已经满的情况下,分裂叶结点,将包括待插入数据在内的数据均分成两个新结点;
5)在分裂的情况下,将新的结点在父节点未出现的最小值插入父节点;
6)父节点重复插入数据的过程,直到没有新结点需要分裂的情况;
7)如果是根结点需要分裂,将它视为有一个空的父节点,重复上述插入数据的过程。
下面以具体的数据演示插入数据的过程
插入数据 7,更新所有所有非叶结点最小值,然后插入 7
插入数据 6,更新所有非叶节点最小值,然后拆分叶结点
拆分结点,将两个结点中未在父节点出现的最小值插入父结点
将拆分的新结点中未在父节点(空结点)出现的最小值插入父节点
当然,为了减少结点的拆分,提高磁盘利用率,减少磁盘 I/O 次数,B+ 树的插入还提供了旋转功能。当叶结点已满但其左右兄弟节点没有满的情况下,B+ 树并不急于去做拆分操作,而是将记录移到当前所在页的兄弟节点上。通常情况下,左兄弟会被先检查用来做旋转操作。
例如如下情况,在插入 85 的情况下可以进行左旋操作
4、B+ 树在 Mysql 中的应用
在 Mysql 中,索引是采用 B+ 树的结构进行组织,从而形成索引文件。索引是属于存储引擎级别的概念,下面主要介绍 MyISAM 和 InnoDB 两个存储引擎的索引实现方式。
4.1、非聚集索引存储引擎 MyISAM
此处借用他人的图片进行说明,MyISAM 存储引擎在 B+ 树的叶结点,存放的是数据记录的地址。下面两种张图分别展示了主键与辅助索引的 B+ 树的结构。
所谓的聚集非聚集是针对叶结点的 data 中是否存储有完整的数据记录来区分的,MyISAM 中 data 只存储数据记录的地址,因而是非聚集索引。
4.2、聚集索引存储引擎 InnoDB
此处同样借用他人的图片进行说明。
从上图可以看出,InnoDB 的所有辅助索引都引用主键作为 data 域。聚集索引使得在利用主键进行查询时,搜索十分高效,而利用辅助索引进行查询是,需要检索两遍索引。
知道InnoDB的索引实现后,就很容易明白为什么不建议使用过长的字段作为主键,因为所有辅助索引都引用主索引,过长的主索引会令辅助索引变得过大。再例如,用非单调的字段作为主键在InnoDB中不是个好主意,因为InnoDB数据文件本身是一颗B+Tree,非单调的主键会造成在插入新记录时数据文件为了维持B+Tree的特性而频繁的分裂调整,十分低效,而使用自增字段作为主键则是一个很好的选择。
转载整理自
[1]: 万字总结:学习MySQL优化原理,这一篇就够了!
[2]: MySQL 索引及查询优化总结
[3]: B+ tree
[4]: B+树的几点总结
[5]: 图解B+树的插入和删除
[6]: MYSQL-B+TREE索引原理
[7]: 从B树、B+树、B*树谈到R 树
[8]: 从MySQL Bug#67718浅谈B+树索引的分裂优化