解析B+树比B树更加适合做数据库索引的原因

前言

数据库常用的索引有Hash索引、B+Tree索引 、全文索引。Hash索引虽然等值查询会很快(单条记录查询),但其无法进行有效的范围查询,而范围查询是数据库的常用操作,因此很多存储引擎更倾向于使用B+Tree索引,例如MyISAM与InnoDB 都是默认使用B+Tree索引(它们的一个区别是InnoDB使用聚集索引,而MyISAM使用非聚集索引)。本文主要是讨论B+树与B树结构差不多,为什么会用B+树而不是B树来做索引。
这里是通过B+树和B树的数据结构角度来分析B+树更适合做索引的原因。而我的另外一篇博文从底层出发,通过分析B+树具体实现索引的原理来解析B+树更适合做索引的原因-《从底层解析B+索引提高查询速度的原因》

B树

B树是平衡的多路查找树,M阶B树具有以下特点:
1.定义任意非叶子结点最多只有M个儿子;且M>2;

2.根结点的儿子数为[2, M];

3.除根结点以外的非叶子结点的儿子数为[M/2, M];

4.每个结点存放至少(M/2)-1(取上整)和至多M-1个关键字;

5.非叶子结点的关键字个数=指向儿子的指针个数-1;

6.非叶子结点的关键字:K[1], K[2], …, K[M-1];且K[i] < K[i+1];(结点内的关键字有序)

7.非叶子结点的指针:P[1], P[2], …, P[M];其中P[1]指向关键字小于K[1]的子树,P[M]指向关键字大于K[M-1]的子树,其它P[i]指向关键字属于(K[i-1], K[i])的子树;(关键字左边指针指向的子树所有关键字值都小于该关键字,右边指针指向的所有子树关键值都大于该关键字)

8.所有叶子结点位于同一层

9.中序遍历B树会得到有序的关键字排列。
在这里插入图片描述

B+树

B+树是B树的变体,也是一种多路搜索树:其定义基本与B-树同,除了以下几点:
1.非叶子结点的子树指针与关键字个数相同或者子树指针数=关键字个数+1;

2.非叶结点仅具有索引下一层作用,不存储数据的指针,跟记录有关的信息均存放在叶结点中。

3.非叶子结点的子树指针P[i],指向关键字值属于[K[i], K[i+1])的子树(B树是开区间);

4.为所有叶子结点增加一个链指针;树的所有叶结点构成一个有序链表,可以按照关键码排序的次序遍历全部记录。

5.所有关键字具体数据或者数据地址都在叶子结点。

三阶B+树(关键字数=孩子指针数)
在这里插入图片描述
四阶B+树(关键字数=孩子指针数-1)
在这里插入图片描述
注意:
B+树有两种实现方式,非叶子结点的子结点数=关键字数,另一种为非叶结点的关键字数=子结点数-1,虽然他们数据排列结构不一样,但其原理还是一样的,Mysql 的B+树是用第一种方式实现。

两者皆可的原因是因为B+树的重点不在于关键字数和孩子指针数之间的关系,而是非叶子结点不存储Data域,叶子结点才存储Data域和叶子结点之间有指针连接,构成有序链表。

分析为什么B+树更适合做索引

我们可以先看看B+树在B树结构之上的改进,B+树在B树的基础之上最重要的改进就是非叶子结点只存储关键字和下一层的索引,不存储Data域,只在叶子结点存储Data域,且为所有叶子结点增加一个链指针,使所有叶结点构成一个有序链表,因此当我们需要有序遍历所有关键字时,直接从最小关键字的叶子结点开始遍历即可。
(注: Data域可以存放具体数据或者是数据的地址,区别在下面详述)

1、非叶子结点不存储Data域的好处
(1)非叶子结点不存储Data域,只是起着目录的作用,使得每一个非叶子结点可以存放更多的关键字和下一层结点的指针。数据库是存储在磁盘上的,我们读取数据是从磁盘读取到内存中,我们在进行磁盘预读取时,是以块(也可称作数据页)的单位进行数据读取,我们在检索B/B+树的结点时,每次以块为单位将一个结点读取到内存中,若一块磁盘包含了树结点以外的数据,就造成了浪费,因此我们需要使每一个结点的容量大小正好或者接近一块磁盘(数据页,一般是16k)的大小。于是我们在构建B+/B树时,树的阶数其实就取决于一块磁盘中能容纳多少个关键字以及相关的索引和Data域。B+树的非叶子结点不存储Data域,因此它可以存储更多个关键字和下一层结点的指针,因此B+树会比B树更矮胖。若我需要查找的关键字正好在叶子结点,因为B+树比B树更矮,B+树所进行的I/O次数更少,因为途中经过每一层,我们都需要进行一次I/O读取一个结点,B+树会更矮,途径的层数会更少。

(2)使得B+树查询速度更稳定,B+树所有关键字数据地址都存在叶子节点上,所以每次查找的次数都相同所以查询速度要比B树更稳定;

2、所有叶结点构成一个有序链表的好处:
(1)B+树便于区间查找(这点才是B+树作为索引的关键),我们进行数据库查询大多为区间查询,B+树天然具备排序功能,B+树所有的叶子结点构成了一个有序链表,在查询大小区间的数据时候更方便,B+树查询,只需通过头结点往下找到第一个叶子结点,然后在叶子结点的链表上就行遍历即可完成区间查询。而且B+树叶子结点是以索引字段大小顺序进行排序的,索引字段大小相邻的数据位置也相邻,因此通过遍历叶子结点链表只需要进行少量I/O读取就能将所有数据都获取。而B树的索引字段大小相邻近的结点可能隔得很远,要想进行区间查询需要不停的进行中序遍历,相邻的元素可能在内存中不相邻,所以缓存命中性没有B+树好。

(2)B+树全结点遍历更快:B+树遍历整棵树只需要遍历所有的叶子节点即可,而不需要像B树一样需要进行中序遍历,这有利于数据库做全表扫描。

聚集索引和非聚集索引

1、聚集索引-数据行存储的物理顺序与聚集索引值的逻辑顺序相同(查询效率快的原因),一个表中只能拥有一个聚集索引,一般默认是主键。例如InnoDB中,使用的就是聚集索引,默认聚集索引建立在主键上,所以数据的物理存储顺序其实就是以主键值的大小顺序进行排序存储。例如数据表中已经插入主键值为1、3、5、7、9的数据(存储顺序以主键值大小顺序进行存储),此时我要插入主键值为4的数据记录,此时磁盘上数据的存储顺序就变成了1、3、4、5、7、9(这也是聚集索引修改慢的原因,插入数据可能造成数据的移动,需要对数据页进行重排序,开销很大,所以一般采取主键自增)。

2、聚集索引的叶子结点就是对应的数据结点,也就是说叶子结点中存储了关键字所在的一行的数据。(Innodb采用聚集索引,数据与索引聚集,Data域存的是数据本身。索引也是数据。数据和索引存在一个XX.IDB文件中,所以也叫聚集索引。)
在这里插入图片描述

3、非聚集索引中索引值的逻辑顺序与磁盘上行的物理存储顺序不同,一个表中可以拥有多个非聚集索引。数据是以聚集索引值的顺序进行存储,但聚集索引只在搜索条件是主键的时候才起作用,因此我们常常使用到非聚集索引,那么如何使用呢?我们需要以非聚集索引值建立一颗新的B+树,比如主键为C1,我们经常以字段C2为条件进行查找,那可以在C2上建立一个非聚集索引,以C2值为关键字建立一棵B+树。当以C2为条件进行查找时,就以C2值 在该B+树上进查找,查找到后,若需要select的字段存在叶结点中,则返回该值,若不在,则需要进行回表操作。
(InnoDB回表操作-InnoDB中叶子结点除了存储了该记录的关键字,还存储了主键值,通过主键值在聚集索引中查找到该具体记录。)
(MyISAM回表操作-MyISAM中叶子结点除了存储该记录的关键字,还存储了行号(物理地址),通过行号去查找具体记录。)

4、非聚集索引的叶子层并不和实际数据页相重叠,而采用叶子层包含一个指向表中的记录在数据页中的指针方式。非聚集索引层次多,不会造成数据重排序。( MyISAM使用非聚集索引,Data域中存的是数据地址。索引是索引,数据是数据。索引放在XX.MYI文件中,数据放在XX.MYD文件中,所以也叫非聚集索引。)
在这里插入图片描述

总结

结点内进行二分查找、整体表现为多路搜索。

B+树优点(选择B+树作为索引的原因)

1、单一结点存储更多的关键字,使得查询的IO次数减少;

2、所有查询都要查找到叶子节点,查询性能稳定;

3、所有叶子节点形成有序链表,便于区间查询以及全结点遍历更快。

B树的优点

如果经常访问的数据离根节点很近,而B树的非叶子节点本身存有关键字的具体数据或者其数据的地址,所以这种数据检索的时候会要比B+树快。有很多基于频率的搜索是选用B树,越频繁query的结点越往根上走,前提是需要对query做统计,而且要对key做一些变化。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章