在MySQL中,索引的的实现方式中使用的最多的就是B+Tree,那么为什么要选择B+Tree呢?我们就需要从最基本的二叉查找树说起
二叉查找树( Binary Search Tree)
又称二叉排序树,那什么是二叉查找树呢,总结来说就是符合如下规则的的就是二叉查找树
- 某节点的左子树节点值仅包含小于该节点的值
- 某节点的右子树节点值仅包含大于该节点的值
- 左右子树每个也必须是二叉查找树
咦,这不是挺平衡的吗,左右对称。没错,这个刚好对称,按照这个逻辑也有不对称的,比如下面这个图
同样符合规则,但及其不平衡,查找次数或时间复杂度 O(h)可能会随着单边树长无限增长。
接下来我们使用动图看下二叉查找树的排序过程:
红黑树(Red-Black Tree)
-
为什么要有红黑树?
解决二叉查找树的不平衡问题。 -
什么是红黑树?
红黑树就是一个自平衡的二叉查找树,树上的每一个节点都遵循如下规则:
- 每个节点都有红色或黑色
- 树的根始终黑色的
- 没有两个相邻的红色节点(相邻指的是父节点或子节点相邻。其中黑色在某些情况下可以连续)
- 从根节点或其它节点出发到任何后代NULL(就是叶子节点之后的节点)节点的每条路径都具有相同数量的黑色节点
这里有两个概念需要弄清楚
-
recolor(根据规则重新标记颜色)
假设着是一个二叉树,然后插入节点值4的节点,根据规则,会触发recolor
插入后发现违反了第三条规则,需要重新调整颜色
这里做了两个调整:
- 将0007调整为黑色,左侧符合要求
- 但右侧从根节点触发没有黑色节点,违反第四条规则,然后把0015也置为黑色后就完全满足红黑树规则了
-
rotation(旋转,主要是这个进行平衡)
首先我们先放置两个节点如下图:
当再插入一个节点0005的时候,二叉树就不平衡了
就会触发rotation进行旋转(可以想象把0007节点提起来),然后重新根据recolor调整颜色,最终符合规则
接下来我我们通过动图看下如何把二叉查找树的不平衡状态调整为平衡状态,这里我们依次插入10、9、8、7节点值的节点
总结来说,步骤如下:
- 节点在符合二叉排序树的规则插入(删除)后
- 判断二叉树是否平衡,如果不平衡则进行
rotaion旋转,就是把中间的节点像线一样提起来 - 然后判断红黑颜色是否符合规则,不符合再进行
recolor(重新调整颜色)
B-Tree(B-树)
定位索引,数据与索引存储在一个节点中,无需再定位数据
-
为什么要使用B-Tree?
红黑树也可以实现B-Tree同样的功能来实现索引,但在MySQL中,索引是存在磁盘上的,对磁盘进行I/O要比对内存进行存取高出好几个数量级,因此使用B-Tree主要目的是减少查找过程中磁盘I/O的存取次数 -
B-Tree规则:
- 所有键值分布在整颗树中
- 任何一个关键字只会出现在一个节点中(没有冗余)
- 搜索有可能在非叶子节点结束(因为key/value都是在一个节点中)
- 通过关键字在全树中做一次查找,性能与二分查找相近
B+Tree(B+树)
先定位索引再通过索引高效快速定位数据
- 为什么使用B+Tree?
-
B+Tree主要是为了比B-Tree
更少的磁盘I/O次数,通过非叶子节点不存储value,让一个节点可以存储更多的key,所以同样一次磁盘I/O操作,B+Tree能够读取更多的索引信息,I/O的效率就更高了这里的I/O是高多少,我们可以来计算下:
在MySQL中InnoDB存储引擎中页默认大小为16KB,假设表的主键类型为bigint(8字节),指针类型也为8字节,也就是一个页(也就是B+Tree的一个节点)可以存储16KB/(8B+8B)=1000个key,如果树的高度为3(就是深度为3)的B+Tree可以维护100010001000=10亿条记录。
如果在B-Tree中,假设data大小为512字节,那么一个节点存储16KB/(8+8+512)=30,深度为3的话B-Tree可以维护303030=27000条记录,着差距显而易见! -
MySQL是一种关系型数据库,区间访问是常见的一种情况,将
每个叶子节点相连,可使用范围区间对叶子节点进行查询(B-Tree非叶子节点也有存数据,无法范围区间查询)
- 与B-Tree的区别
- 所有非叶子节点仅存储key,不存储value(只有叶子节点才存储真正的数据)
- 为所有叶子节点之间增加一个链指针
- 一个关键字key必定会在叶子节点出现(带value),也有可能在非叶子节点出现(不带value)
- 与红黑树用途区别
- 红黑树更多用于内部排序,需要排序的数据都在内存中,比如Map、Set就有用到红黑树
- B+树主要是为文件存储而生(比如索引文件是放在磁盘中),一个节点一般对应一个block,避免树形结构不断向下查找,然后磁盘不停寻道、读数据,减少了I/O次数。
- B+数据只需要遍历叶子节点就可以实现整棵树遍历,而其它树形结构需要中序遍历(中左右)才能访问所有数据
总结
从二叉排序树到B+Tree,发现每一种树的发展都是由历史原因的,到了B+Tree中,磁盘I/O存取次数最少、深度最浅、单节点保存数据最多,只需要将ROOT节点存储在内存中,就可以很快的在10亿条数据中查找记录。
扩展
- 局部性原理
为了提高I/O效率,磁盘中默认有预读处理,比如读取一个字节,磁盘会顺序向后读取一定长度(一般为页的倍数,通常为4k)的数据放入内存
MySQL就是利用了磁盘预读原理,将B+Tree节点大小设计为一个页的大小,保证一个节点对应一个物理上的一个页,加上计算机存储分配都是按页对其,所以每个节点仅需一次I/O操作。包括叶子节点使用链指针连接也是为了符合局部性原理,提高查找效率
参考资料
- 姜承尧 著;MySQL技术内幕-InnoDB存储引擎;机械工业出版社,2013
- 由 B-/B+树看 MySQL索引结构
- MYSQL-B+TREE索引原理
- Algorithm Visualizations