数据结构与算法简记--B+树

B+树


争哥的课程都会我们解决问题的思路和过程比结果更重要

此节就以这样的思路来分析MYSQL的索引底层数据结构B+树是如何一步一步演化的

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

对一些模糊的需求进行假设,来限定要解决的问题的范围:

分析mysql数据库的需求

  • 功能性需求

假定只有以下两个需求:

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

安全、性能、用户体验,着重考虑性能需求

性能需求中着重考虑执行效率和存储空间,即时间复杂度和空间复杂度

  • 尝试用学过的数据结构解决这个问题
    • 散列表:查询性能很好:O(1),不支持按区间查询
    • 平衡二叉查找树:查询:O(logn),依然不能解决按区间查询
    • 跳表:查询,增,删效率都很好:O(logn),跳表结构:多层索引,底层数据使用链表串联,查询区间,可先查到起始数据,再在链表中顺序遍历直到找到结束数据,满足需求,实际B+树的结构跟跳表就很像。
  • 改造二叉查找树来解决这个问题
    • 用跳表的思想改造二叉树,少量数据时,已可满足需求
    • 数据规模比较大时,如给一亿个数据构建二叉查找树索引,那索引中会包含大约 1 亿个节点,每个节点假设占用 16 个字节,那就需要大约 1GB 的内存空间,如表多,索引也多,内存就无法满足需求了。

这时使用空间换时间的思路,可将索引存储在磁盘中。

然而磁盘的性能跟内存比相差上万倍,影响磁盘性能的主要瓶颈在于磁盘io操作,减少磁盘IO操作可提高性能,意味着要一次读取尽可能多的节点。

如果把二叉树存储在磁盘中,那每次节点的读取都对应一次磁盘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; // 这个结点在链表中的后继结点
    }

m的大小并不是越大越好,在选择 m 大小的时候,要尽量让每个节点的大小等于一个页的大小(getconf PAGE_SIZE)。读取一个节点,只需要一次磁盘 IO 操作。

数据库的索引对增删数据的影响

猜你喜欢

转载自www.cnblogs.com/wod-Y/p/12161694.html