我们在日常中经常操作数据库,mysql数据库是常用的数据库之一,支持多种索引类型,如B+Tree,哈希索引,全文索引等。这次主要研究的就是常用的B+Tree也叫B+树。
什么是B+树?
B+ 树是一种树数据结构,通常用于数据库和操作系统的文件系统中。B+ 树的特点是能够保持数据稳定有序,其插入与修改拥有较稳定的对数时间复杂度。B+ 树元素自底向上插入,这与二叉树恰好相反。但是光说概念无疑是晦涩的,下面就来看一下为什么B+树可以作为数据库的主要索引之一。
索引的本质
MySQL官方对索引的说明是索引是帮助MySQL高效获取数据的数据结构。提取句子主干,就可以得到索引的本质:索引是数据结构。为什么这里要讲查询算法和数据结构呢?因为之所以要建立索引,其实就是为了构建一种数据结构,可以在上面应用一种高效的查询算法,最终提高数据的查询速度。
要了解数据库索引的底层原理,我们就得先了解一种叫树的数据结构,而树中很经典的一种数据结构就是二叉树!所以就从二叉树到平衡二叉树,再到B树(B-树),最后到B+树来一步一步了解数据库索引底层的原理
二叉树
二叉树是每个结点最多有两个子树的树结构。通常子树被称作“左子树”(left subtree)和“右子树”(right subtree)。二叉树常被用于实现二叉查找树和二叉堆。二叉树有如下特性:
1、每个结点都包含一个元素以及n个子树,这里0≤n≤2。
2、左子树和右子树是有顺序的,次序不能任意颠倒。左子树的值要小于父结点,右子树的值要大于父结点。
那么假设现在有这样一组数[35 27 48 12 29 38 55],顺序的插入到一个数的结构中,得到的结果就会是下面这样。
但是这样的数据对于二叉树有一个问题就是,假如每次插入的数据都比上一个要大,那么这棵树的长度就会越来越大,因为他始终都要在右边插入,这样就变成了一个类似于队列的结构,这无疑是会严重影响查询速度的,所以,为了加快查询速度,就在二叉树的基础上衍生出了平衡二叉树
平衡二叉树
平衡二叉树是一种特殊的二叉树,所以他也满足前面说到的二叉树的两个特性,同时还有一个特性:
它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。
这样这棵树就始终满足二叉树的几个特性而保持平衡,树也不会退化为线性链表了,需要查找一个数的时候就能沿着树根一直往下找,这样的查找效率是很快的,查询次数也会大幅度缩减。
但是数据库的数据是存储于硬盘中的,也就是说每次我们查找读取数据,是要通过磁盘的IO进行读写,这个读的效率是固定并且损耗性能的,暂时无法突破,那么假如数据量过于庞大,我们理论最坏需要进行查询的次数变的越来越多的话,就会导致效率变得很低,那么在这个问题上,又进行了优化,我们可以一次多查询一些数据,或者说让树的每一层可以存储更多的节点,这就有了B-Tree(B树)
B-Tree
那B-Tree有哪些特性呢?一棵m阶的B-Tree有如下特性:
1、每个结点最多m个子结点。
2、除了根结点和叶子结点外,每个结点最少有m/2(向上取整)个子结点。
3、如果根结点不是叶子结点,那根结点至少包含两个子结点。
4、所有的叶子结点都位于同一层。
5、每个结点都包含k个元素(关键字),这里m/2≤k<m,这里m/2向下取整。
6、每个节点中的元素(关键字)从小到大排列。
7、每个元素(关键字)字左结点的值,都小于或等于该元素(关键字)。右结点的值都大于或等于该元素(关键字)。
示例如下:
从这个图中我们能看出,B-Tree的查询效率好像也并不比平衡二叉树高。但是查询所经过的结点数量要少很多,也就意味着要少很多次的磁盘IO,这对性能的提升是很大的。
B-Tree的元素就是类似1、2、3这样的数值,但是数据库的数据都是一条条的数据,如果某个数据库以B-Tree的数据结构存储数据,普通的B-Tree的结点中,元素就是一个个的数字。B-Tree把元素部分拆分成了key-data的形式,key就是数据的主键,data就是具体的数据。这样我们在找一条记录的时候,就沿着根结点往下找就ok了,效率是比较高的。
但是IO在读取硬盘的文件的时候也是有限制的,它一次只能读取约4KB的数据,在结点中存储数据无疑也是要浪费IO的读写的,假如一条记录的索引对应的数据过大,大于了4KB的话,那么这一次IO的读写就无法读取其他的记录,导致在树的一层可能会耗费多次IO的读,所以在这基础上又进行了优化,就有了B+Tree(B+树)。
B+Tree
B+Tree是在B-Tree基础上的一种优化,使其更适合实现外存储索引结构。B+Tree与B-Tree的结构很像,但是也有几个自己的特性:
1、所有的非叶子节点只存储关键字信息。
2、所有卫星数据(具体数据)都存在叶子结点中。
3、所有的叶子结点中包含了全部元素的信息。
4、所有叶子节点之间都有一个链指针。
B-Tree 和 B+Tree的选择
在讲这两种数据结构在数据库中的选择之前,我们还需要了解的一个知识点是操作系统从磁盘读取数据到内存是以磁盘块(block)为基本单位的,位于同一个磁盘块中的数据会被一次性读取出来,而不是需要什么取什么。即使只需要一个字节,磁盘也会从这个位置开始,顺序向后读取一定长度的数据放入内存。这样做的理论依据是计算机科学中著名的局部性原理: 当一个数据被用到时,其附近的数据也通常会马上被使用。
预读的长度一般为页(page)的整倍数。页是计算机管理存储器的逻辑块,硬件及操作系统往往将主存和磁盘存储区分割为连续的大小相等的块,每个存储块称为一页(在许多操作系统中,页得大小通常为4k)。
那么B-Tree和B+Tree该如何选择呢?都有哪些优劣呢?
B-Tree因为非叶子结点也保存具体数据,所以在查找某个关键字的时候找到即可返回。而B+Tree所有的数据都在叶子结点,每次查找都得到叶子结点。所以在同样高度的B-Tree和B+Tree中,B-Tree查找某个关键字的效率更高。
由于B+Tree所有的数据都在叶子结点,并且结点之间有指针连接,在找大于某个关键字或者小于某个关键字的数据的时候,B+Tree只需要找到该关键字然后沿着链表遍历就可以了,而B-Tree还需要遍历该关键字结点的根结点去搜索。
由于B-Tree的每个结点(这里的结点可以理解为一个数据页)都存储主键+实际数据,而B+Tree非叶子结点只存储关键字信息,而每个页的大小有限是有限的,所以同一页能存储的B-Tree的数据会比B+Tree存储的更少。这样同样总量的数据,B-Tree的深度会更大,增大查询时的磁盘I/O次数,进而影响查询效率。
鉴于以上的比较,所以在常用的关系型数据库中,都是选择B+Tree的数据结构来存储数据