B+树:MySQL数据库索引是如何实现的???

作为一个软件开发工程师,大家对数据库肯定是再熟悉不过了。主流的数据存储系统,在业务开发中有着很重要的地位。在工作中常常为了加速数据库中数据的查找速度,常用的思路就是对表中数据创建索引。那么想没想过数据库索引是如何实现的呢?底层使用的是什么数据结构和算法呢???

算法解析

1、解决问题的前提是定义清楚问题

如何定义清楚问题呢?除了对问题进行详细的调研之外还可以通过对一些模糊的需求进行假设,来限定要解决的问题的范围。

对数据库非常了解,针对这个问题能把索引的需求定义得非常清楚。但是大部分可能只是了解一小部分常用的SQL,因此假设要解决的问题只包含两个常用的需求:

  1. 根据某个值查找数据:select * from user where id=1234;
  2. 根据区间值来查找某些数据:select * from user where id > 1234 and id < 2345;

除了这些功能性需求之外往往还会涉及一些其他的非功能性需求,如安全、性能、用户体验等等。本文对于非功能性需求着重考虑性能方面的需求。性能方面的需求主要考察时间和空间两个方面就是执行效率和存储空间。。。

在执行效率方面,希望通过索引,查询数据的效率尽可能的高;在存储方面希望索引不要消耗太多的内存空间。

2、尝试用学过的数据结构来解决这个问题

知道了需求,那么能否利用已经学习过的数据结构去解决问题呢?支持快速查询、插入等操作的动态数据结构,学习过散列表、平衡二叉树、跳表。。。

  • 散列表
    查询性能很好,时间复杂度是O(1)。但是不能支持按照区间快速查找数据,因此散列表不能满足我们的需求。
  • 平衡二叉查找树
    查询的性能也很高,时间复杂度是O(logn)。对树进行中序遍历,还可以得到一个从小到大的数据序列,但是仍然不足以支持按照区间快速查找数据。
  • 跳表
    跳表是在链表之上加上多层索引构成的。支持快速插入、查找、删除数据,时间复杂度是O(logn)。跳表也支持按照区间快速查找数据。只需要定位到区间起点值对应在链表中的结点,然后从这个结点开始顺序遍历链表,直到区间终点对应的结点为止,这期间遍历得到的数据就是满足区间值的数据。
    在这里插入图片描述
    这样看来,跳表是可以解决问题的。实际上数据库索引所用到的数据结构跟跳表是非常相似的叫做B+树。它是通过二叉查找树演化过来的,并非跳表。接下来先从二叉查找树开始看它是如何一步一步被改造成B+树的。。

3、改造二叉查找树来解决这个问题

为了让二叉查找树支持按照区间来查找数据,可以对它进行改造:

  • 树中的结点并不存储数据本身,而是只是作为索引。
  • 把每个叶子节点串在一条链表上,链表中的数据是从小到大有序的。

在这里插入图片描述
改造之后,如果我们要求某个区间的值。那么只需要拿区间的值在树中查找当查找到某个叶子节点之后,再顺着链表往后遍历,直到链表中的结点数据值大于区间的终止值为止。所有遍历到的数据就是符合区间值的所有数据。
在这里插入图片描述
但是如果要为很多的数据构建索引,如果索引存储在内存中,尽管内存访问的速度非常快,查询的效率非常高,但是占用的内存会非常多。。。

数据很多的情况下建立索引要消耗很多的内存空间,且对内存的需求是无法满足的。如何解决这个索引占用太多内存的问题呢???

可以借助时间换空间的思路,将索引存储在硬盘中并非内存中。硬盘比内存的读取速度要慢很多、将索引存在硬盘中尽管是减少了内存消耗,但是在查找过程中需要读取索引因此数据查询效率相应降低很多。。。

二叉查找树改造之后支持区间查找的功能就实现了。但是为了节省内存,如果将树存储到硬盘中,每个节点的读取(或访问)都对应一次IO操作。树的高度就等于每次查询数据时磁盘IO操作的次数。。。

因此我们优化的重点就是尽量减少磁盘IO操作,就是尽量降低树的高度,那么应该如何做呢???

如果把索引构建成m叉树高度是不是比二叉树要小呢?图所示给16个数据构建二叉树索引,树的高度是4查找一个数据需要4个磁盘IO操作(如果根节点存储在内存中其他节点存储在硬盘中),如果m叉树中的m是100,那对一亿个数据构建索引树的高度也只是3,最多只要3次磁盘IO就能获取到数据。磁盘IO变少了,查找数据的效率也就提高了。。。
在这里插入图片描述
如果将m叉树实现B+树索引,使用代码实现出来,就是下面这个样子(假设给int类型的数据库字段添加索引,所以代码中的keywords是int类型的):

public class BmoreTreeDemo {
    /**
     * 这是 B+树非叶子节点的定义
     *
     * 假设keywords=[3,5,8,10]
     * 4个键值将数据分为5个区间:(-INF,3),[3,5),[5,8),[8,10),[10,INF)
     * 5个区间分别对应:children[0]...children[4]
     *
     * m值是事先计算得到的,计算的依据是让所有信息的大小正好等于页的大小:
     * PAGE_SIZE = (m-1)*4[keywords大小]+m*8[children大小]
     */

    public static int m=5;//5叉树
    public int[] keywords = new int[m-1];//键值,用来划分数据区间
    public BmoreTreeDemo[] children = new BmoreTreeDemo[m];//保存子节点指针

}

/**
 * 这是B+树中叶子节点的定义
 *
 * B+树中的叶子节点跟内部节点是不一样的,
 * 叶子节点存储的是值,而不是区间。
 * 这个定义里,每个叶子节点存储3个数据行的键值及地址信息
 *
 * k值是事先计算得到的,计算的依据是让所有信息的大小正好等于页的大小:
 * PAGE_SIZE = k*4[keywords]+k*8[dataAddress大小]+8[prev大小]+8[next大小]
 */

class BPlusTreeLeafNode{
    public static int k =3;
    public int[] keywords = new int[k];//数据的键值
    public long[] dataAddress = new long[k];//数据地址

    public BPlusTreeLeafNode prev; //这个结点在链表中的前驱节点
    public BPlusTreeLeafNode next; //这个结点在链表中的后继节点
}

对于相同个数的数据构建m叉树索引,m叉树中的m越大,树的高度就越小,那么m叉树中的m是不是越大越好呢?到底是多大才最合适呢???

不管是内存中的数据还是硬盘中的数据,操作系统都是按页来读取的,一次会读一页的数据。如果要读取的数据量超过一页的大小就会触发多次IO操作,在选择m大小的时候要尽量让每个节点大小等于一个页的大小。读取一个节点,只需要一次磁盘IO操作。。。
在这里插入图片描述
尽管索引可以提高数据库的查询效率,但是索引也是有弊端的,它会让写入数据的效率下降,那么这又是为什么呢???

主要的原因就是数据的写入过程会涉及到索引的更新。。。

对于一个B+树来说,m值是根据页的大小事先计算好的,就是说每个节点最多只能有m个子节点。在往数据库中写入数据的过程中,这样有可能使索引中某些节点的子节点个数超过m,这个节点大小超过了一个页的大小,读取这样一个节点就会导致多次磁盘IO操作,那么该如何解决这个问题呢???

实际上处理思路不是很复杂,只需要将这个节点分裂成两个节点。节点分裂之后其上层父节点子节点个数可能超过m个。可以将父节点分裂成两个节点,级联反应从下往下,一直影响到根节点。(下图中B+树是一个三叉树,限定叶子节点中,数据个数超过2个就分裂节点;非叶子节点,子节点个数超过3个就分裂节点)。
在这里插入图片描述
要时刻保证B+树索引是一个m叉树,因此索引的存在会导致数据库写入的速度降低,实际上不光写入数据会变慢,删除也会变慢,这是因为什么呢???

在删除某个数据的时候也要对应的更新索引节点,有点类似跳表中删除数据的思路。频繁的删除数据,会导致某些结点中,子节点个数变得非常少,很长时间以后如果每个节点的子节点都比较少,势必会影响索引的效率。。

可以设置一个阈值。B+树中这个阈值等于m/2。如果某个节点的子节点个数小于m/2,就将它跟相邻的兄弟节点合并。不过合并之后子节点个数可能会超过m。针对这种情况,可以借助插入数据时候的处理方法,再次分裂节点。。。

下面是一个删除操作的例子,可以对比下(图中的 B+ 树是一个五叉树。我们限定叶子节点中,数据的个数少于 2 个就合并节点;非叶子节点中,子节点的个数少于 3 个就合并节点。)
在这里插入图片描述
B+树的结构和操作跟跳表非常相似,理论上来讲对跳表稍加改造可以替代B+树,作为数据库的索引实现的。。

总结一下B+树的特点:

  1. 每个节点中子节点个数不能超过m,也不能小于m/2
  2. 跟节点的子节点个数可以不超过m/2,这是一个例外
  3. m叉树只存储索引,并不真正存储数据,这个有点类似跳表
  4. 通过链表将叶子节点串联在一起,这样可以方便按区间查找
  5. 一般情况下,根节点会被存储在内存中,其他节点存储在磁盘中

B树实际上是低级版的B+树,或者说B+树是B树的改进版。B树和B+树的不同点主要集中在这几个地方:

  1. B+树中的节点不存储数据,只是索引而B树中的节点存储数据
  2. B树中的叶子节点并不需要链表来串联

也就是说B树只是一个每个节点的子节点个数不能小于m/2的m叉树。。。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值