B树、B+树

文章参考:

终于把B树搞明白了(三)_B树的查找,B+树的引入_哔哩哔哩_bilibili

MySQL体系构架、存储引擎和索引结构_mysql segment为什么分为叶节点和非叶节点-CSDN博客

B树、B+树详解 - Assassinの - 博客园 (cnblogs.com)

一. B树

1. 概述

1.1 为什么使用B树

对数据进行IO的耗时:影响IO的耗时有很多,这里主要将两点:

  • 数据块的大小:磁盘IO的单位为磁盘块,将IO的磁盘块中的数据以页的形式存放在内存中。一般磁盘块和页的大小由操作系统决定,因此如果数据块过大,需要进行多次IO。
  • IO的次数:这个涉及到数据的寻址次数,如二叉排序树,不同节点随机存放在不同的磁盘地址,在进行查找时,二分查找保证IO的最多次数为\(log(n)\)

以往数据结构的缺点:

  • 二叉排序树:遇到顺序插入会退化为链表。IO次数过多。
  • 平衡二叉树:写的代价过高,需要进行多次旋转。
  • 红黑树:数据量较大时,树的深度将会难以控制。会导致IO次数增多。

B树的核心思想:

因此我们需要改进,如果我们将节点的最大子节点树修改为M个而不2个,那么树的深度将会得到有效控制,此时纵向的IO次数大大减少。

此外,我们将每个节点的所有字节点顺序存储,放在一块内存中,这样一次IO就可以读到某个节点的所有子节点,随后在内存中对这些子节点进行对比。避免了横向的多次IO。

1.2 B树的定义

B树是一种多路搜索树,一棵m阶(每个节点最多有m个子节点)的B树满足如下条件:

  • 树中每个节点至多有m个孩子。
  • 根节点的儿子数为\([2, m]\)
  • 除根节点以外的非叶子节点的儿子数量为\([m/2, m]\)
  • 每个节点存放的关键字(就是索引,用于排序的):
    • 至少为\([m/2 - 1]\)(向上取整,且不能少于2个)
    • 最多为\(m-1\)个。因为关键字之间是指向子节点的指针,而每个节点的字节最多为m个,那么每个节点的指针最多也是m个,所以关键字最多为m-1个。
  • 非叶子节点的关键字:\(k[1],k[2],...,k[m-1]\),有\(k[i]<k[i+1]\)
  • 非叶子节点的指针:\(p[1],p[2],...,p[m]\),其中:
    • \(p[1]\)指向的子树的所有关键字小于\(k[1]\)
    • \(p[m]\)指向的子树所有关键字大于\(k[m-1]\)
    • \(p[i]\)指向的子树的所有关键字大于\(k[i-1]\),小于\(k[i]\)
  • 所有叶子节点位于同一层。

1.3 一个3阶b树

image-20240330135405313

从中可以得出以下结论:

  • 指针存在为空的情况。
  • B树中索引和该索引对应的记录(数据)存储在一起。
  • 有时不需要读到叶子节点即可命中。
  • 每个节点存在一块内存中,一次IO可以读取一块节点。

2. B树的搜索

逻辑:具体逻辑如下:

  1. 从根节点开始。
  2. 对当前节点,采用二分查找,如果命中,那么找到数据;否则进入3。
  3. 查找该关键字所在范围的指针指向的儿子节点,重复2、3操作。
  4. 如果3中对应范围的儿子节点为空,或者该节点已经是叶子节点,说明不存在该关键字,查找结束。

3. 插入

4. 删除

二. B+树

1. 概述

1.1 为什么要使用B+树

B树的缺陷:

B树将关键字和该关键字对应的记录绑定,一同记录在节点中。因为数据的大小是浮动的,且有很多时候数据会很大,而B树中节点的大小一般是固定的(一页或页的整数倍),这会导致每个节点中关键字数量较少。又因为\(指针数量=关键字数量+1\),所以指针数量也会较少,那么该节点的字节点数量就会较少。树的宽度降低,那么深度自然增加。结果就是IO次数又变多了。

B树子节点数量限制:假设操作系统为64位,关键字为int,一行数据大小为1kB,节点的大小为一页(16kB),那么每个节点最多有几个子节点。

  1. 64位操作系统,指针大小为8B,关键字大小为4B。

  2. 假设指针m个,关键字m-1个,那么:

    \[8m+(m-1)*(1k+4)=16k\\\\ k\approx 17 \]

而当每条记录数据量较大时,例如每条数据大小为8kB,那么一个节点就只有两个关键字了,显然,这会导致树的深度再次加深。

B+树的核心思想:

非叶子节点中不再记录数据,只存储指针和关键字,而数据只存储在叶子节点中。有以下优势:

  • 一个节点可以存储众多指针和关键字,树的宽度大大增加。
  • 只在叶子节点才会读到数据,十分稳定。

1.2 B+树的定义

B+树是一种B树的变体,也是一种多路搜索树。对于一棵m阶B+树,有:

  • 和B树一样的:

    • 树中每个节点至多有m个孩子。

    • 根节点的儿子数为\([2, m]\)

    • 除根节点以外的非叶子节点的儿子数量为\([m/2, m]\)

    • 每个节点存放的关键字(就是索引,用于排序的):

      • 至少为\([m/2 - 1]\)(向上取整,且不能少于2个)
      • 最多为\(m-1\)个。因为关键字之间是指向子节点的指针,而每个节点的字节最多为m个,那么每个节点的指针最多也是m个,所以关键字最多为m-1个。
    • 非叶子节点的关键字:\(k[1],k[2],...,k[m-1]\),有\(k[i]<k[i+1]\)

  • 和B树不一样的:

    • 非叶子节点的子树指针和关键字数量一致。
    • 非叶子节点的子树指针\(p[i]\),指向的子树关键字范围是:\([ k[i], k[i+1] )\)(注意是左闭右开)
    • 为所有的叶子节点增加一个链指针。
    • 所有的关键字在叶子节点中均出现。

    1.3 一个3阶B+树

image-20240330160515116

其中:

  • 所有的关键字都在叶子节点出现,以链表的形式顺序存储。
  • 因为只有叶子节点存有数据,因此只有在叶子节点才能命中。
  • 非叶子节点相当于叶子节点的索引。
  • 每一个叶子节点都包含一个指向下一个叶子节点的指针。这方便了叶子节点的范围遍历
  • 更加适合文件索引系统。

2. B+树的搜索

和B树类似,区别主要有两点:

  • B+树必须到叶子节点才能命中,因为只有叶子节点存储了数据。
  • B+树叶子节点之间通过指针相连,遍历十分方便。而B树的遍历则只能通过中序遍历才能得到递增的序列。

3. 为什么MySQL使用B+树而非B树作为索引结构

  1. B+树的IO次数更少。

    • B+树非叶子节点只用来存储关键字和指针,不存储数据。因此非叶子节点可以有更多的子节点。树的深度得以降低,平均IO次数更少。
    • B树中所有节点中的关键字和数据均成对出现,即便是在非叶子节点中。因此单个非叶子节点容纳的关键字数量较少,树的宽度被限制,深度增加,平均IO次数增加。
  2. B+树的查询效率更稳定。

    • B+树将所有数据存储在叶子节点,因此每次查询都要到叶子节点。
    • 而B树则可能在非叶子节点命中。
  3. B+树更加擅长范围查找:

    • B+树的叶子节点通过指针双向指针相连,可以直接遍历。
    • B树的范围查找只能通过中序遍历进行,效率较低。
  4. B+树更加适合遍历。

    • B+树将所有数据和对应关键字存在叶子节点中,不同叶子节点之间通过指针相连,因此遍历只需要扫描一遍叶子节点即可。
    • B树非叶子节点也有数据,因此遍历要走所有节点,也需要遍历那些没有数据的节点。
posted @ 2024-03-30 16:26  BinaryPrinter  阅读(20)  评论(0编辑  收藏  举报