(一)二叉查找树

MySQL索引为什么用B+Tree?_结点

二叉查找树 检索是

10是根节点,假如我想查询 7 的数据,检索步骤是 一个节点一个节点去比对,先把根节点加载到内存里面, 7 和 10比对发现比10小,就会往左边进行搜索.这时候把5加载到内存里面,去跟7比对,发现 7 比5大,就往右边走. 然后把 7加载到内存里面发现是我要的数据.这样的话就可以过滤掉 15 节点后面的数据,大大减少了检索的时间.

二叉树也会出现问题,在连续插入的时候如果后面的插入的数据依次都比前面的大,就会变成是链表的形式.这样就和全表扫描没有什么不同了.

MySQL索引为什么用B+Tree?_加载_02

二叉树检索的效率取决于你数据的分布.

(二)平衡二叉树

平衡二叉树定义: 某一个节点的子节点的高度差不会超过1

平衡二叉树插入的时候:

假如插入44的时候

MySQL索引为什么用B+Tree?_数据_03

这时候发现左边没有节点,然后22的子节点高度超过1了,就会旋转一下, 让33变为子节点.

MySQL索引为什么用B+Tree?_加载_04

*平衡二叉树缺点:*

它太深了

数据处的(高)深度决定着他的io操作次数,io操作耗时大 ,比如说100w条数据的平衡二叉树的高度大概在20左右,也就是说从有100w条数据的平衡二叉树中找一个数据,最坏的情况下需要20次查找。如果是内存操作,效率也是很高的!但是我们数据库中的数据基本都是放在磁盘中的,每读取一个二叉树的结点就是一次磁盘IO,这样我们找一条数据如果要经过20次磁盘的IO?那性能就成了一个很大的问题了

它太小了

每一磁盘块儿(节点/页)保存的数据量太小了

没有很好的利用操作磁盘IO的数据交换特性,也没有利用好磁盘IO的预读能力(空间局部性原理),从而带来频繁的IO操作

*什么是IO的预读能力?*

系统给我们定义的一个空间局部性原理,操作系统在你做IO操作的时候它会认为你既然读一个文件,文件有2mb大小,你把头部的4kb读回来以后,它会认为你会用到它接下来的另外4Kb或者8Kb 或者是16kb. 假如说你加载一个图片,你先给头部4kb 操作系统交换的数据单位是以页为单位,一页数据是4kb. 比如说把头部的4kb加载回来以后,所有操作系统认为你既然加载了4kb回来了,操作系统认为你还会认为你马上就会用到相邻的数据空间,所以它在加载数据时候,操作系统不是只加载一页数据的大小,它会加载16k或者24k等等.

MySQL一页数据是16k,MySQL一次性交互就是16k . 一次读操作系统的4页就相当于MySQL的1页

*那为什么平衡二叉树不适合作为索引呢?*

索引是存在于索引文件中,是存在于磁盘中的。因为索引通常是很大的,因此无法一次将全部索引加载到内存当中,因此每次只能从磁盘中读取一个磁盘页的数据到内存中。而这个磁盘的读取的速度较内存中的读取速度而言是差了好几个级别。

注意,我们说的平衡二叉树结构,指的是逻辑结构上的平衡二叉树,其物理实现是数组。然后由于在逻辑结构上相近的节点在物理结构上可能会差很远。因此,每次读取的磁盘页的数据中有许多是用不上的。因此,查找过程中要进行许多次的磁盘读取操作。

(三)多路平衡查找树B-Tree

MySQL索引为什么用B+Tree?_加载_05

什么是路,路就是叉 , 多路就是比2还要多, 上面图就是一个节点有3个分支岔路.

假如查找 15 ,先加载根节点数据, 15 跟17 比对, 比17还要小的话,就会加载左侧P1的节点, 如果大于17小于35的话就会走中间P2的节点,通过这样的方式进行数据的寻址. 这样能找到更多的数据, 才三次IO操作就能有14个数据.

操作系统做一次IO操作的话就加载4KB的数据,一个MySQL一页是16kb, 一个节点是 16kb. 假设一个int类型的id索引是 4byte大小+算上其它冗余内存.的话, 差不多一个节点能保存2000多个关键字, 第二层就是2000多个关键字加1路 .

我们在定义数据类型的时候字段的长度尽量要精简一点,也就是字段的长度,比如说保存电话号码, 如果你要定义成256字节也能保存,但是会造成空间浪费,万一你要作为索引的话,它会拖垮整个分叉路数, 因为一页最多16kb 节点多了 分叉路数就少了.

索引不要建太多,要建合适的:

为什么,因为你要删除数据,它为了保证树的绝对平衡,它会做了很多逻辑判断.

在建立索引的时候要建立利用率高的, 利用率不高的要及时删除掉.

海量数据的时候,在你插入数据的时候,它需要在你插入之后对索引进行更新

(四)B+树

MySQL索引为什么用B+Tree?_结点_06

*查询方式:*

主键索引区:PI(关联保存的时数据的地址)按主键查询,

普通索引区:si(关联的id的地址,然后再到达上面的地址)。所以按主键查询,速度最快

*B+tree性质:*

1.)n棵子tree的节点包含n个关键字,不用来保存数据而是保存数据的索引。

2.)所有的叶子结点中包含了全部关键字的信息,及指向含这些关键字记录的指针,且叶子结点本身依关键字的大小自小而大顺序链接。

3.)所有的非终端结点可以看成是索引部分,结点中仅含其子树中的最大(或最小)关键字。

4.)B+ 树中,数据对象的插入和删除仅在叶节点上进行。

5.)B+树有2个头指针,一个是树的根节点,一个是最小关键码的叶节点。

只有最底层的叶子节点(文件)保存数据)非叶子节点只保存索引,不保存实际的数据,数据都保存在叶子节点中

是左闭合区间的一种方式,为什么要左闭合,它推从的是一种默认的id作为索引,也就是数字作为索引,基本上变化就是自增,所以,一般来说变化就是往它的右边进行数据的插入.

为什么用闭合区间,支节点(中间的那行)不保存数据,只保存关键字和引用关系,数据全部保存在叶子结点(第三行)

为什么选用B+树而不是B树

B+是B-树的变种(plus版) 多路绝对平衡查找树,他拥有b-树的优势

B+树扫库,表能力更强

B+树的磁盘读写能力更强(枝节点不保存数据,这样的话加载关键字信息更多)

B+树的排序能力更强(天然具有排序顺序性)

B+树的查询效率更加稳定(因为不管树有多高,必须要进行最末梢叶子结点去寻址)

1、 B+树的磁盘读写代价更低:B+树的内部节点并没有指向关键字具体信息的指针,因此其内部节点相对B树更小,如果把所有同一内部节点的关键字存放在同一盘块中,那么盘块所能容纳的关键字数量也越多,一次性读入内存的需要查找的关键字也就越多,相对IO读写次数就降低了。

2、B+树的查询效率更加稳定:由于非终结点并不是最终指向文件内容的结点,而只是叶子结点中关键字的索引。所以任何关键字的查找必须走一条从根结点到叶子结点的路。所有关键字查询的路径长度相同,导致每一个数据的查询效率相当。

3、由于B+树的数据都存储在叶子结点中,分支结点均为索引,方便扫库,只需要扫一遍叶子结点即可,但是B树因为其分支结点同样存储着数据,我们要找到具体的数据,需要进行一次中序遍历按序来扫,所以B+树更加适合在区间查询的情况,所以通常B+树用于数据库索引。

PS:我在知乎上看到有人是这样说的,我感觉说的也挺有道理的:

他们认为数据库索引采用B+树的主要原因是:B树在提高了IO性能的同时并没有解决元素遍历的我效率低下的问题,正是为了解决这个问题,B+树应用而生。B+树只需要去遍历叶子节点就可以实现整棵树的遍历。而且在数据库中基于范围的查询是非常频繁的,而B树不支持这样的操作或者说效率太低。

B+树和B树区别

1.树和B+树最重要的一个区别就是B+树只有叶节点存放数据,其余节点用来索引,而B-树是每个索引节点都会有Data域

2.B+叶子节点是顺序排列的,并且相邻节点具有顺序引用的关系.(最后一个节点是有天然排序能力的,最后一个数据会指向下一节点的第一个数据.)

(五)总结

1)二叉查找树(BST):解决了排序的基本问题,但是由于无法保证平衡,可能退化为链表

2)平衡二叉树(AVⅥL):通过旋转解决了平衡的问题,但是旋转操作效率太低

3)红黑树:通过舍弃严格的平衡和引入红黑节点,解决了AⅥ旋转效率过低的问题,但是在磁盘等场景下,树仍然太高,IO次数太多

4)B树:通过将二叉树改为多路平衡查找树,解决了树过高的问题

5)B+树:在B树的基础上,将非叶节点改造为不存储数据的纯索引节点,进一步降低了树的高度;此外将叶节点使用指针连接成链表,范围查询更加高效。