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

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

软件开发工程师为了加速数据库存储系统的查找速度,对表中数据创建索引,数据库索引如何实现?底层用的是什么数据结构和算法?

算法解析

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

定义清楚问题就是通过对一些模糊的需求进行假设来限定要解决的问题的范围

假设要解决的问题只包含两个常用的需求

  • 根据某个值查找数据,比如select * from user where id=1234;
  • 根据区间值来查找某些数据,比如select * from user where id>1234 and id<2345

希望通过索引查询数据的效率尽可能高,还不消耗太多的内存空间

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

B+树通过二叉查找树演化来的

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

为了让二叉查找树支持按照区间来查找数据,进行改造:树中节点并不存储数据本身,只是作为索引,且每个叶子节点串在一条链表上,链表中数据是从小到大有序的

改造之后如果我们要求某个区间的数据,只需要拿区间的起始值在树中查找,当查找到某个叶子节点之后,顺着链表往后遍历,直到链表中结点数据值大于区间的终止值为止,所有遍历到的数据就是符合区间值的所有数据

如果要为几千万、上亿数据构建索引,如果将索引存储在内存中,尽管访问速度很快,但是占用的内存很高,比如给一亿个数据构建二叉查找树索引,索引中包含1亿个节点,每节点占用16字节,需要1GB内存空间,如果要给10张表建立索引,如何解决索引占用太多内存的问题?

如果把树存储在硬盘中,每个节点的读取都对应一次磁盘IO操作,树的高度等于每次查询数据时磁盘IO操作的次数,那么尽量减少磁盘IO操作,如何降低树的高度呢?

将m叉树实现B+树索引(假设给int类型的数据库字典添加索引,所以代码中keywords是int类型):

/**
 * 这是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[keywordss大小]+m*8[children大小]
 */
public class BPlusTreeNode {
  public static int m = 5; // 5叉树
  public int[] keywords = new int[m-1]; // 键值,用来划分数据区间
  public BPlusTreeNode[] children = new BPlusTreeNode[m];//保存子节点指针
}

/**
 * 这是B+树中叶子节点的定义。
 *
 * B+树中的叶子节点跟内部结点是不一样的,
 * 叶子节点存储的是值,而非区间。
 * 这个定义里,每个叶子节点存储3个数据行的键值及地址信息。
 *
 * k值是事先计算得到的,计算的依据是让所有信息的大小正好等于页的大小:
 * PAGE_SIZE = k*4[keyw..大小]+k*8[dataAd..大小]+8[prev大小]+8[next大小]
 */
public 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; // 这个结点在链表中的后继结点
}

内存中数据还是磁盘中数据,操作系统都是按页(一页都是4KB,这个值可以通过getconfig PAGE_SIZE)来读取的,一次读一页的数据,所以选择m大小的时候尽量让每个节点的大小等于一个页的大小,读取一个节点只需要一次磁盘IO操作

索引有利有弊,会让写入数据的效率下降,因为数据的写入过程会涉及索引的更新,对于一个B+树,m值是根据页的大小事先计算好的,即每个节点最多只能有m个子节点,有可能在写入过程中,使索引中某结点的子节点个数超过m,如何解决?

只需要将这个节点分裂成两个节点,将父节点也分裂成两个节点,一直到根节点。删除节点的时候也会变慢。

可以设置一个阈值,在B+树中,这个阈值=m/2,如果某个节点的子节点个数<m/2,将它与相邻的兄弟节点合并,不过,合并之后节点的子节点个数可能会超过m,可以分裂节点

举删除操作例子:(B+树是五叉树,叶子节点中数据个数<2就合并节点,非叶子节点中,子节点个数<3就合并节点)

总结

B+树特点:

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

B树就是B-树,也是低级版B+树,B树跟B+树的区别是:

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

B+树中,将叶子节点串起来的链表是双向链表还是单链表?

双向链表

发布了75 篇原创文章 · 获赞 9 · 访问量 9166

猜你喜欢

转载自blog.csdn.net/ywangjiyl/article/details/104770428