B树和B+树的区别
节点构造
B-树内部节点是保存数据的;而B+树内部节点是不保存数据的,只作索引作用,它的叶子节点才保存数据。
根据链接的代码我们可以了解到一个节点:
B-树: 保存Entry数据项以及子树指针。
B+树: 保存关键字以及子树指针。
在B+树 代码链接中的Java代码还是有部分缺陷的。
B+树相比与B树的一个重要特性就是它的内部节点不存储数据,只保存关键字信息用于索引。
如果数据仅仅为一个值key,B+树的优势就只有可以范围查询了(下文介绍),并且因为数据冗余存储花销大于B树。
但是如果存储的数据很大,我们以mysql索引为例,通过B+树存储数据,一行数据中,一般都有一个键,以及其他附属的列,称为值,那么这个键值对关系就比较明确了。
B+树构造索引:内部节点仅存放键,叶子节点存放所有的行数据信息。这时B+树相对于B树的优势就展现出来了。
优势一: B+树查找更快+mysql预读特性
为什么?
因为以下两个特性:
1. B+树内部节点不存储真实数据,将真实数据放到叶节点
使用B+树构造索引,它的内部节点定义可以是这样:
https://www.codeleading.com/article/13672521751/ 参考
private class BPlusTreeNode{
int nodeTotal=0; // "Max" = 5 ; 记录本节点个数
int[] values=new int[MaxOrder]; // 用于存储主键元素
BPlusTreeNode[] children=new BPlusTreeNode[MaxOrder];
而使用B树构造索引,它的内部节点会是这样:
private class BTreeNode{
int nodeTotal=0; // "Max" = 5 ; 记录本节点个数
private List<Entry<K, V>> entrys; //存储数据项
BTreeNode[] children=new BTreeNode[MaxOrder];
对于B树来说,一个Entry的大小取决于数据的大小,如果存储的行数据很大,那么B树每个节点需要存储的数据项占用空间也会很大,但是B+树不会,它完全可以只保存一个int数组来存放行数据的主键信息用于索引就好了,将真实数据放到叶节点存储。
2. 顺序读取和局部性原理
可以直接说明,mysql的索引存放于表空间,每一个节点可以认为是一个块(页面),从内存中读取索引时顺序读取时常常会预读取(额外读取后面的连续块)。
提问:这个时候是不是需要追求对整个索引一次读取到的越多越好?
当然,如果一次能够读取到整个索引,就不需要二次IO了。
这就是上面特性一:B+树内部节点不存储真实数据,将真实数据放到叶节点的好处。
因为B+树内部节点不存储真实数据,将真实数据放到叶节点,所以索引的内部节点占用空间更少,一次IO读取到的信息更多,定位满足条件的数据行就会更快。
优势二:B+树面向修改更快
还是根据特性一:B+树内部节点不存储真实数据,将真实数据放到叶节点
我们知道一个树结构它的插入和删除操作可能会对整棵树的结构产生影响,比如分裂、换位等等,如果节点又过于臃肿,那么插入和删除会花销很大,相反如果节点存储的数据很少,就会减少这个花销。
B+树因为特性一所以它的修改和删除的花销相比于使用B树会小一些。
优势三(比较重要的优势) B+树可以实现范围查找
其他优势都属于性能优势,如果你可以容忍部分性能损失,使用B树也可以,而这个优势是B+树契合于数据库索引的重要原因,是B树所无法替代的。
所有叶子都在相同的高度上,叶结点本身按关键字大小从小到大链接。
innodb实现叶子节点之间产生连接,所以我们通常说innodb通过B+树来实现。
所以我们在这里谈B树和B+树只是在谈标准的B树和B+树特性,真正在使用过程并不一定就是严格的、我们通常理解的B+树。
在B+树 代码中也表达了这一困惑,其实也说明我们通常定义的标准数据结构并非一定要在使用中采用,而是根据应用灵活改变。
索引为什么使用B+树?
通过上面的分析这个答案应该已经明确了。但是需要说明,B+树比B树更适合做索引仅仅是因为更契合数据库索引的使用场景。B+树和B树没有优劣一说,二者根据不同的场景可以发挥不同的优势。
这是一种非常重要的思想,使用数据结构一般从三个方面考量:
- 业务场景(mysql使用了预读场景,B+树更合适,数据库一般处理的数据量极大,B+树内部节点不保存真实数据查询效率、修改效率都会更好。)
- 功能要求(范围查询,B+树可以做到)
- 性能要求 (在脱离业务场景下,显然B树的查询效率更高。但是因为业务场景的加持,这一点性能要求是随着业务场景的变化而变化的。此时的B+树比B树更好。)