数据结构 - B+树(B树)

目录

1、B+树

2、B+树 VS B树

3、Mysql为什么选择B+树?


    理解B+树的数据结构一定在理解了树、平衡二叉搜索树的基础上。再梳理一下,平衡二叉树是为了让树本身更加的”丰满“,近似于满二叉树(或者完全二叉树),降低树的层高【查询效率与树的层高有关】,防止极端情况树退化成时间复杂度为O(N)的链表。二叉查找树是当每个节点做一次判断,就可以选择左子或右子树进行查询,每次讲数据查询范围缩小为原来的一半。 基于平衡二叉查找树,整个树的读写的时间复杂度近似O(logN),效率是非常高的,之前分析过当N为42亿次时,O(logN)查询次数是32,而O(N) 查询次数是42亿次。平衡二叉查找树有:AVL树(自平衡二叉查找树)、树堆(Treap)、伸展树,以及工程上经常使用的高性能-红黑树。

    我们想到数据库(Mysql/Oracle)就会想到B树、B+树,反之也是一样的,如果只是基于性能考虑红黑树完全够了。但是数据库需要存储大量的数据,并且一般需要保证事务(ACID特别是持久化),即数据库的数据必须落地(磁盘)。基于计算机的内存寻址查询的性能是纳秒级别的,而磁盘寻址的查询性能是毫秒级别的,性能对比是万倍,十万倍的差距。所以需要控制查询磁盘的次数,即控制树的高度,显然平衡二叉查找树是不行的,所以需要将二叉变成多叉,B+树本身就是多路平衡查找树。从磁盘中获取数据到缓存中,则需要涉及到操作系统的缓存页问题,一般缓存页为4K左右。我们要完全利用其特性,所以Mysql中的多路默认为1200。

    此时,如果存储17亿的数据,层高仅为4。 如果考虑到根节点的数据存储在内存中,那么最多磁盘寻址三次,如果经常查询的数据,第二层数据节点的缓存页也在内存中,那么只需要最多磁盘寻址两次。数据到底在磁盘中还是内存中,Mysql使用了LRU的缓存淘汰算法,并且将整个链表分成 5/8 的old区和 3/8的young区域(用于防止某一次的全表扫描查询操作导致缓存命中率降低)。

1、B+树

    站在数据结构的角度,看看B+树的特性如下(下面的m就是多路平衡查找树中的多字):

  • 每个节点的子节点个数不能超过m,也不能小于 m/2.
  • 根节点的子节点个数可以小于 m/2,这是特殊于上一条的点
  • m叉树只存储索引,并不存储真实的数据,这个与跳表一样
  • 通过链表将叶子节点连接在一起,这样就可以按照区间查询
  • 一般情况下,根节点会被存储在内存中,其他节点存储在磁盘中(依据上面分析的数据库缓存淘汰算法)

平衡二叉查找树为了维持:查找、平衡两个特性,当发生新增、删除操作时,需要树本身也维持这两个特性,AVL树本身需要对节点进行左右旋转。而红黑树更加复杂,除了旋转还需要更改节点的颜色。B+树本身也不例外,只是树的高度已经非常低了,并且每个节点内部的个数非常大。此时左右旋转已经不适用了,对应新增删除需要进行数据页的分裂和合并,用于满足节点个数为【m/2,m】。

    下面是https://www.cs.usfca.edu/~galles/visualization/BPlusTree.html中,degree为3、7的时候生成的两棵B+树(自行脑补 1200路的样子):

数据结构 - B+树(B树)_第1张图片

数据结构 - B+树(B树)_第2张图片

 

2、B+树 VS B树

    B-树本身就是B树,都是翻译写法的原因导致我也是误导了很久,哈哈。B树在数据库中的使用也非常广泛,比如 MongoDB、Oracle。下面是B树区别于B+树的特点:

  • B+树中的节点不存储数据,只是索引(即数据全部在叶子节点上),而B树中的节点存储数据
  • B树中的叶子节点,没有使用链表指针链接
  • 即:B树只是一棵每个节点的子节点个数不能小于m/2的 m叉树

数据可以存储在非叶子节点中,所以使用链表进行链接本身没有意义(会很混乱)。数据不存储在叶子节点上,那么查询一个数据的时候,可能在根节点就查询返回了,可能在第2层查询返回了,也可以能在叶子节点(最高处)查询返回,那么查询本身就兼容了查询的最好、最坏时间复杂度。 而B树每次查询都需要到叶子节点,即B+树查询性能是稳定的

数据结构 - B+树(B树)_第3张图片

 

3、Mysql为什么选择B+树?

    个人理解:需求梳理 -> 功能(需要支持的方法)+ 性能 -> 算法 + 数据结构

    Mysql本质是数据库,需要存储大量的数据和查询,并且Mysql基本都会使用InnerDB引擎,支持事务特性ACID。进行梳理前还需要考虑一个历史因素,数据库是一个“古老”的东西,B+树出现在1972年,而跳表出现在我出生那一年(1989)。梳理一下需要满足的功能:

1、sql:create、update、delete、select

    即数据库本身需要支持大量的读写操作,那么选择的数据结构本身需要支持快速的读写数据结构,时间复杂度不能太高。

    读写时间复杂度为O(logN) 的数据结构都可以考虑,此时维护可以基于二分查找的有序数组,不能满足大量的写操作(非静态数据)。只是Mysql的B+树内部数据节点(数据页),内部查询可以理解成二分查找。

    读写时间复杂度为O(logN)的有红黑树、跳表,只是红黑树本身比较适合存储在内存中使用。读写时间复杂度近似O(1)的散列表也是可选的。

2、等值查询:select * from table where id = 1

    基于等值查询,有序数组、红黑树、跳表、散列表本身都是可行的

3、范围查询:select * from table where id > 30

    基于范围查询红黑树需要每次都从根节点查询,即满足条件的有1000条则要执行1000次根节点开始查询,执行 1000 * O(logN)。而B+树利用叶子节点使用链表进行存储,查询下一个节点的时间复杂度是O(1).

    散列表通过散列函数查询对应的值,本身要求散列函数计算的值越随机、越散列越好,跟支持有序刚好相反(基于LinkedHashMap的 散列表+双向链表此处是可行的)。

    跳表本身是满足范围查询的。

4、order by(group by)支持排序、join操作、count(*)

    这些都是在内存中,基于 join buffer、sort buffer(文件排序)等实现,只是这些都依赖数据结构的查询速度。

 

 

你可能感兴趣的:(数据结构&算法,数据结构,数据库,mysql,B+树,B树)