数据结构 - B, B+树原理及其在MySQL中的应用

数据结构 - B, B+树原理及其在MySQL中的应用

该文章介绍B,B+树的原理及其在MySQL中的应用,包含以下内容,

  • 背景
  • B树简介
  • B树的查找,增加,删除
  • B+树简介
  • B+树的查找,增加,删除
  • B+树在MySQL中的应用

背景

B树和B+树一般应用于文件系统或数据库系统,其能够在很大程度上提高文件系统或数据库系统的查询性能。这里有一个疑问,文件系统或数据库为什么要使用B,B+树,而不使用AVL,红黑树等呢?其实,单从查询的逻辑上来说,AVL和红黑树的效率要高于B,B+树,因为前者都是二叉查找树,而B,B+树是m(一般情况m>2)叉查找树。因为在某个节点内,B,B+树存在顺序查找的可能,因此,其查询性能要低于AVL和红黑树。但在文件系统或数据库系统中,总的查询时间还包括从磁盘读取数据到内存等这些操作的时间,而这些时间相对于在内存中比较数据的时间来说要高很多数量级,因此在文件系统或数据库系统中,真正的瓶颈是磁盘IO操作。

对数据库系统而言,通常使用索引来提高查询效率,索引一般以文件的形式存储到磁盘,由于索引文件可能很大(可达到几十上百G甚至更大),一般不会一次全部加载到内存,只有在使用的时候才按需加载。每加载一次索引块就需要一次磁盘IO,所以,需要尽量减少磁盘IO的次数。在操作系统中,一次IO至少加载一页,大小通常为4K。但根据局部性原理磁盘一般会预读更多的数据,因为当一个数据被用到时,其附近的数据也通常会马上被使用,所以MySQL数据库一页的大小是16K。如果把树中的某个节点信息都存放到一页,由于AVL和红黑树的深度远远大于B,B+树,所以查询数据时要检查更多的节点,就需要更多的磁盘IO,其性能也就不如B,B+树了。这就是为什么在文件系统或数据库应用中采用B,B+树,而不是AVL和红黑树的原因。

B树简介

B树也称B-树或B-Tree,是一种多路平衡查找树。AVL和红黑树都是二叉树,而B树是m叉树,m称为B树的阶,所以这里叫多路。平衡是指任何节点的所有子树都一样高,其平衡性比AVL树还好。为什么B树能保持这么好的平衡性呢?这与其增加和删除元素的方式有关,后面会提到。最后,B树是查找树,其最终目的是快速查找数据,这点和AVL及红黑树一样。

B树有以下特点:

  1. 一个节点可以存储2个及以上的元素,可以拥有2个及以上的子节点。
  2. 拥有二叉查找树的基本特点。
  3. 平衡性很好,高度很矮。

m(m>=2)阶B树的性质:

  1. 根节点元素个数范围:\large [1, m - 1]
  2. 非根节点元素个数范围:\large [\left \lceil m/2 \right \rceil - 1, m - 1],其中,\large \left \lceil m/2 \right \rceil是向上取整的值。
  3. 根节点的子节点个数范围:\large [1, m]
  4. 非根节点的子节点个数范围:\large [\left \lceil m/2 \right \rceil, m],注意:叶子节点无子节点。

例如,5阶B树,根节点元素个数介于[1, 4]之间,非根节点元素个数介于[2,4]之间。

5阶B树又称为(3, 5)树,5代表阶,3代表非根节点至少包含3个子树。

同理,3阶B树可称(2, 3)树,4阶B树可称(2, 4)树,6阶B树可称(3, 6)树,7阶B树可称(4, 7)树,m阶B树可称为\large (\left \lceil m/2 \right \rceil, m)树。

下图是一个4阶B树,

在数据库应用中,m一般是200或300左右。

B树和二叉查找树在逻辑上是一样的,二叉查找树通过父子合并即可得到B树。

B树的查找,增加,删除

查找

B树的查找与二叉查找树的查找基本类似,只是B树在判断当前节点时需要依次判断当前节点内的所有元素,如果命中则返回,否则到对应的子节点中查找。例如,查找值为15的元素,查找路径为:

增加

增加操作一定会把元素增加到叶子节点,增加后,如果叶子节点中元素的个数没有超过最大限制则返回,否则称叶子节点上溢(overflow),解决上溢的步骤:

  1. 求出叶子节点中间元素的位置k。
  2. 将位置k的元素与父节点所有元素合并。
  3. 将叶子节点分裂成两个子节点 [0, k-1] 和 [K+1, m-1],叶子节点的父节点增加指针指向两个新的叶子节点。
  4. 将步骤1-3依次应用到所有的父节点并一直到root,因为父节点增加一个元素后也有可能会上溢,如果root节点也上溢,则会生成一个新的root节点,此时树的高度会增加1。

下图是一个4阶B树增加元素后发生溢出的示例:

删除

删除操作最终删除的一定是叶子节点中的元素,分两种情况,

  1. 如果删除的是叶子节点元素,则直接删除对应的元素。
  2. 如果删除的是非叶子节点元素,需要找到待删除节点的前驱或后继节点元素,交换待删除节点元素和其前驱或后继节点的元素值,然后删除前驱或后继节点的元素。

我们知道,非根节点中元素个数至少为\large \left \lceil m/2 \right \rceil - 1,上面两种情况在删除元素后,如果节点中元素的个数小于该值(一定是\large \left \lceil m/2 \right \rceil - 2)则称为下溢(underflow),解决下溢的步骤:

  1. 如果下溢节点的兄弟节点个数大于等于\large \left \lceil m/2 \right \rceil,则向其兄弟节点借一个元素。此时兄弟节点元素,父节点元素,下溢节点会进行一次旋转操作。注意,兄弟节点借出一个元素后依然满足元素个数最低要求。
  2. 如果无法借用兄弟节点元素,则将父节点中对应的元素挪下来,与下溢节点,下溢节点的兄弟节点一起合并成一个新的节点。注意,合并后的新节点元素依然满足最大元素个数要求。
  3. 如果步骤2发生,则将步骤1-2应用到所有父节点并一直到root,因为父节点挪走了一个元素后有可能产生下溢。如果root也产生下溢,则树的高度会减1。

下图是一个4阶B树删除元素后产生下溢但可以向兄弟节点借一个元素的示例:

下图是一个4阶B树删除元素后产生下溢并无法借用兄弟节点元素的示例:

为什么B树是平衡的?

只有增加操作且root节点产生了上溢才会使B树的高度增加,root节点产生上溢会分裂成一个新的root节点和两个子节点,此时root的两个子节点的高度都会同时加一,所以增加操作不会破坏B树的平衡性。

只有删除操作且root节点产生了下溢才会使B树的高度减少,root节点产生下溢会删除原root节点并生成一个新的root节点,此时新root节点的所有子节点高度都会减一,所以删除操作不会破坏B树的平衡性。

综上所述,B树一直是保持平衡的,且任何节点的所有子树都一样高,是绝对的平衡。

B+树简介

B+树是B树的一种变形,比B树具有更广泛的应用,其在满足B树的一般性质下(元素和节点的个数范围要求),还具备以下不同点,

  1. B+树的非叶子节点不存放关键字对应的真实数据,只存放关键字本身,只有叶子节点才存放关键字对应的真实数据。而B树的非叶子节点会存放关键字对应的真实数据。
  2. 所有叶子节点都有一个指向下一个叶子节点的指针,利用该指针我们可以方便的查询到当前元素的下一个元素的位置,对于DB中的范围查找有很大的帮助。B树没有该指针。
  3. B+树在增加元素后如果出现上溢,对叶子节点而言,分裂后的其中一个节点依然保留了上溢节点的中间元素,即新的父节点元素。B树分裂后上溢节点的中间元素只存在于父节点中,子节点不会保存。
  4. B+树在删除元素后如果出现下溢且无法向兄弟节点借用元素时,对叶子节点而言,合并后的节点不包含父节点中对应的元素,对非叶子节点而言,会合并父节点对应的元素。B树会始终合并父节点中对应的元素。

下图是一颗4阶B+树,

B+树的查找,增加,删除

查找

由于真实数据都存放于叶子节点,所以B+树的查找会从根节点开始一直查找到某个叶子节点,即便某个非叶子节点包含了待查找的元素,例如:

增加

与B树一样,增加元素最终也是增加到叶子节点,增加后叶子节点可能出现上溢,解决上溢的步骤:

  1. 计算出上溢节点中间元素的位置k。
  2. 将位置k的元素与父节点所有元素合并。
    1. 如果上溢节点是叶子节点,则将该叶子节点分裂成两个子节点[0, k - 1]和[k, m - 1],叶子节点的父节点增加指针指向两个新的叶子节点。
    2. 如果上溢节点是非叶子节点,则将该节点分裂成两个子节点[0, k - 1]和[k - 1, m - 1],叶子节点的父节点增加指针指向两个新的叶子节点。
  3. 将步骤1-2应用到所有父节点并一直到root,因为父节点增加元素后也可能出现上溢,如果root节点也上溢,则生成新的root节点,此时树的高度增加1。

下图是一个4阶B+树增加元素后产生上溢的示例:

删除

删除操作一定是删除叶子节点中的元素,B+树不用像B树一样需要寻找前驱或后继元素,直接删除叶子节点中的元素即可。删除元素后可能出现下溢,解决下溢的步骤:

  1. 如果下溢节点的兄弟节点的元素个数大于等于最低要求\large \left \lceil m/2 \right \rceil,则向其借用一个元素。
    1. 如果下溢节点是叶子节点,则将兄弟节点元素挪至下溢节点元素的位置,并用兄弟节点元素的下一个元素覆盖父节点相应的位置。
    2. 如果下溢节点不是叶子节点,则进行一次旋转,此时的处理逻辑与B树类似。
  2. 如果无法向兄弟节点借用元素则需要合并节点。
    1. 如果下溢节点是叶子节点,则将下溢节点,兄弟节点,父节点对应元素合并成一个新的节点。注意:新节点不包含父节点的元素。
    2. 如果下溢节点不是叶子节点,则将下溢节点,兄弟节点,父节点元素合并成一个新的节点。注意:新节点包含父节点的元素。
  3. 如果步骤2发生,则将步骤1-2应用到所有父节点并一直到root,因为父节点减少一个元素后也可能发生下溢,如果root发生下溢,则生成新的root节点,此时树的高度减一。

下图是一个4阶B+树删除节点后产生下溢但可以借用兄弟节点元素的示例:

下图是一个4阶B+树删除节点后产生下溢且无法借用兄弟节点元素的示例:

为什么B+树是平衡的?

这点与B树类似,因为只有root节点产生上溢后树的高度才增加,只有root节点下溢后树的高度才减少,所以B+树是一直平衡的。

B+树在MySQL中的应用

MySQL底层使用的是B+树,相对于B树,B+树有以下优点:

  1. B+树的非叶子节点不存放真实数据,所以其非叶子节点就能存放更多的元素,这样能提高B+树的阶,最终减少B+树的高度,减少磁盘IO,提高查询性能。
  2. B+树的叶子节点有一个指向下一个叶子节点的指针,在范围查询时效率更高。

MySQL有两个重要的存储引擎,MyISAM InnoDB,下面分别介绍。

MyISAM存储引擎

MyISAM的数据库表对应三个文件,table.frm,table.MYI,table.MYD,

表类型作用
table.frm数据库表本身的定义,包含哪些字段,字段类型,长度,约束,主键等等。
table.MYI存放表的所有索引。
table.MYD存放表的真实数据。

可以看出,索引和真实数据存放在不同的文件。假设有以下表数据:

该表有三列,左侧的0x07,0x56,0x6A等是对应行的指针,可以简单理解成物理地址。通过该指针,MySQL能够非常快速的定位到对应行的物理磁盘位置,从而查询到该行数据。

我们以列Col1列作为主键建立索引,对应的B+树储存形式如下图所示:

若以Col2列建立索引,得到的辅助索引结构和上面的主键索引的结构类似:

所以,MyISAM中的索引都是等价的,相互之间没有依赖关系。

InnoDB存储引擎

InnoDB数据库表对应两个文件,table.frm,table.ibd,

表类型作用
table.frm数据库表本身的定义,包含哪些字段,字段类型,长度,约束,主键等等。
table.ibd存放表的所有索引和真实数据。

InnoDB表有以下特点:

  1. 表数据文件包含数据本身和索引。
  2. 表数据文件本身就是按B+树组织的一个索引结构文件,该索引称为聚集索引。
  3. 聚集索引中叶节点包含了完整的真实数据记录。
  4. InnoDB表必须要有主键,并且推荐使用整型自增主键,通过主键可以得到一个主索引。

如果我们在设计表结构时没有显式指定索引列的话,MySQL会从表中选择数据不重复的列建立索引,如果没有符合的列,则MySQL自动为InnoDB表生成一个隐含字段作为主键,并且这个字段长度为6个字节,类型为整型。

还是以上面的数据为例,假设列Col1是主键,其对应的B+树存储结构为:

可以看出,通过主索引就可以直接查询到真实的数据。

若以列Col3建立辅助索引,对应的B+树存储结构为:

此时,叶子节点存放的值是主索引的值,如果要查询真实数据,还需要借助主索引的B+树结构,因为真实数据全部都存放在主索引的B+树结构中。

InnoDB为什么推荐使用整型自增主键而不是UUID呢?

  1. UUID虽然是唯一的,但其是字符串,会比整型消耗更多的存储空间。
  2. 在B+树中进行查找时需要跟经过的节点值比较大小,整型数据的比较运算比字符串更快。
  3. 自增的整型索引在磁盘中会连续存储,在读取一页数据时也是连续,UUID是随机产生的,读取的上下两行数据存储是分散的,不适合执行where id > 5 && id < 20的条件查询语句。
  4. 在插入或删除数据时,整型自增主键会在叶子结点的末尾建立新的叶子节点,不会破坏左侧子树的结构。UUID主键很容易出现这样的情况,B+树为了维持自身的特性,有可能会进行结构的重构,消耗更多的时间。

所以,对于InnoDB存储引擎,最好使用自增的整型字段作为主键,而不是采用其它字段,因为维护主索引有一定的开销,且其它辅助索引都依赖于主索引。

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值