B树和B+树

B树和B+树

参考文章:

  1. https://blog.csdn.net/xiaojin21cen/article/details/99830864
  2. https://blog.csdn.net/weixin_43156699/article/details/117216784
  3. https://blog.csdn.net/qq_45814695/article/details/117171536

B树

平衡二叉树又称 AVL 树,在满足二叉查找树特性的基础上,要求每个节点的左右子树的高度差不能超过 1。

1970年,R.Bayer和E.mccreight提出了一种适用于外查找的树,它是一种平衡的多叉树,称为B树(或B-树、B_树)。

B树家族是严格平衡树,左右子树的高度差为0,左树和右树高度完全相等

B树(又称B-树,英语:B-tree)是一颗平衡的N叉搜索树(平衡多路查找树),B树中的每个值都是一个复合值

image-20230315134709098

  1. B树的 阶数 是指 树中 的 最多子节点个数。比如,2-3树的阶是3,2-3-4树的阶是4;
  2. B-树的搜索,从根结点开始,对结点内的关键字(有序)序列进行二分查找,如果命中则结束,否则进入查询关键字所属范围的儿子结点;重复,直到所对应的儿子指针为空,或已经是叶子结点;
  3. 关键字集合分布在整棵树中, 即叶子节点和非叶子节点都存放数据;
  4. 搜索有可能在非叶子结点结束;
  5. 其搜索性能等价于在关键字全集内做一次二分查找;

m阶的B 树 (m叉树)的特性

  1. 如果是根结点

​ 根结点至少包含一个关键字,1<=关键字数<=m-1;

​ 2 <= 根结点的孩子数 <= m;

  1. 非叶子节点(除根结点和叶子结点外):

​ ceil(m/2) <= 其包含的孩子数 <= m,

​ 非叶子节点包含的关键字数 = 它的孩子数-1。

  1. 叶子结点都出现在同一层

    ceil(m/2)-1 <= 结点包含的 关键字数 <= m - 1;

  2. 每个结点中的 关键字 从小到大排列,且key[i] 和 key[i+1] 之间的孩子节点的值介于 key[i] 和 key[i+1] 之间。
    【即,父结点中的第i个关键字(如果存在) < 第i个孩子中的所有关键字 < 父结点中的第i+1个关键字(如果存在)】

【注:ceil(x):向上取整】

B树的插入操作

插入操作是指插入一条记录,即(key, value)的键值对。如果B树中已存在需要插入的键值对,则用需要插入的value替换旧的value。若B树不存在这个key,则一定是在叶子结点中进行插入操作。(即,B树自底向上插入,这与二叉树恰好相反

1)根据要插入的key的值,找到叶子结点并插入。

2)判断当前结点key的个数是否小于等于m-1,若满足则结束,否则进行第3步。

3)以结点中间的key为中心分裂成左右两部分,然后将这个中间的key插入到父结点中,这个key的左子树指向分裂后的左半部分,这个key的右子支指向分裂后的右半部分,然后将当前结点指向父结点,继续进行第3步。( 当阶数m为偶数时,需要分裂时就不存在排序恰好在中间的key,那么我们选择中间位置的前一个key或中间位置的后一个key为中心进行分裂即可。

**例子:**以5阶B树为例,根B定义规则2( 至少有Math.ceil(m/2)-1个关键字 ),在5阶B树中,结点最多有4个key,最少有2个key。

image-20230315143749428

image-20230315143853473

B树的删除操作

删除操作是指,根据key删除记录,如果B树中的记录中不存对应key的记录,则删除失败。

1)如果当前需要删除的key位于非叶子结点上,则用后继key(这里的后继key均指后继记录的意思)覆盖要删除的key,然后在后继key所在的子支中删除该后继key。此时后继key一定位于叶子结点上,这个过程和二叉搜索树删除结点的方式类似。删除这个记录后执行第2步

2)上一步中改变过的结点key的个数大于等于Math.ceil(m/2)-1,结束删除操作,否则执行第3步。

3)如果兄弟结点key个数大于Math.ceil(m/2)-1,则父结点中的key下移到该结点,兄弟结点中的一个key上移,删除操作结束。

​ 否则,将父结点中的key下移与当前结点及它的兄弟结点中的key合并,形成一个新的结点。原父结点中的key的两个孩子指针就变成了一个孩子指针,指向这个新结点。然后当前结点的指针指向父结点,重复上第2步。

有些结点它可能即有左兄弟,又有右兄弟,那么我们任意选择一个兄弟结点进行操作即可。

image-20230315150722929

image-20230315150753449

image-20230315150817682

性能分析

对任意一棵具有n个关键字的m阶B-树,其树高h至多为:logt ((n+1)/2)+1,即log以t=ceil(m/2)为底, ((n+1)/2)+1 的对数。

由上述定理可知:B-树的高度为O(log n)。于是在B-树上查找、插入和删除的读写盘的次数为O(logt n),CPU计算时间为O(mlogt n)。

  1. n个关键字的平衡二叉排序的高度(lg n)比B树的高度约大lg t倍。

  2. 作为在内存中使用的表结构B树不一定比平衡二叉树好,尤其当m较大时更是如此。
    这是因为B树增删改查的CPU时间:O(mlogtn)=0(lgn·(m/lgt))

    而m/lgt>1,因此当m较大时,O(mlogtn)比平衡二叉排序树的CPU时间O(lgn)大得多。故,若B树仅作为在内存中使用,则应取较小的m。(通常取最小值m=3,此时B树每个内部结点孩子数目可为2或3,这种3阶的B-树称为2-3树)。

B+树

B+树是B树的一种变形形式,通常用于数据库操作系统文件系统中。B+树上的叶子结点存储关键字以及相应记录的地址,叶子结点以上各层作为索引使用。

image-20230315171944273

B+ 树中各个页之间是通过双向链表连接的,叶子节点中的数据是通过单向链表连接的。其实上面的 B 树我们也可以对各个节点加上链表。这些不是它们之前的区别,是因为在 MySQL 的 InnoDB 存储引擎中,索引就是这样存储的。即,上图中的 B+ 树索引就是 InnoDB 中 B+ 树索引真正的实现方式,准确的说应该是聚集索引

B+树特性

(1)每个结点的关键字个数与孩子个数相等,所有非最下层的内层结点的关键字是对应子树上的最大关键字,最下层内部结点包含了全部关键字。

(2)根结点至少有两个子女;

​ 除根结点以外,每个内部结点有 [m/2] 到m个孩子。

(3)所有叶结点在树结构的同一层,并且不含任何信息(可看成是外部结点或查找失败的结点),因此,树结构总是树高平衡的。

B树和B+树的区别

image-20230315152921729

  1. B+树中,子结点中存在的最大值(最小值)是在父节点中出现过的值(==> 最底层的叶子结点包含了整个数据的全集 ==> 查询效率更加稳定

  2. B+树中,最底层的叶子节点使用链表链接(==> 更高效的区间/范围查询

  3. 对B+树来说,所有的数据都存储在叶子结点,非叶子节点只需要保存索引列的值(==> 非叶子结点占用的空间小,可以直接放在内存中,减少磁盘IO

B+树的查找

B+树的查找与B树不同,当索引部分某个结点的关键字与所查的关键字相等时,并不停止查找,应继续沿着这个关键字左边的指针向下,一直查到该关键字所在的叶子结点为止。

B+树的插入和删除

与B树类似,仅更换关键字和子节点个数的限制条件即可

磁盘IO与B树

磁盘IO与预读

计算机存储设备一般分为两种:内存储器(main memory)和外存储器(external memory)。

  1. 内存储器为内存,内存存取速度快,但容量小,价格昂贵,而且不能长期保存数据(在不通电情况下数据会消失)。

  2. 外存储器即为磁盘读取,磁盘读取数据靠的是机械运动,每次读取数据花费的时间可以分为寻道时间、旋转延迟、传输时间三个部分。

    1) 寻道时间指的是磁臂移动到指定磁道所需要的时间,主流磁盘一般在5ms以下;

    2) 旋转延迟就是我们经常听说的磁盘转速,比如一个磁盘7200转,表示每分钟能转7200次,也就是说1秒钟能转120次,旋转延迟就是1/120/2 = 4.17ms;

    3) 传输时间指的是从磁盘读出或将数据写入磁盘的时间,一般在零点几毫秒,相对于前两个时间可以忽略不计。

那么访问一次磁盘的时间,即一次磁盘IO的时间约等于5+4.17 = 9ms左右,听起来还挺不错的,但要知道一台500 -MIPS的机器每秒可以执行5亿条指令,因为指令依靠的是电的性质,换句话说执行一次IO的时间可以执行40万条指令,数据库动辄十万百万乃至千万级数据,每次9毫秒的时间,显然是个灾难。

考虑到磁盘IO是非常高昂的操作,计算机操作系统做了一些优化,当一次IO时,不光把当前磁盘地址的数据,而是把相邻的数据也都读取到内存缓冲区内,因为局部预读性原理告诉我们,当计算机访问一个地址的数据的时候,与其相邻的数据也会很快被访问到。每一次IO读取的数据我们称之为一页(page)。具体一页有多大数据跟操作系统有关,一般为4k或8k,也就是我们读取一页内的数据时候,实际上才发生了一次IO,这个理论对于索引的数据结构设计非常有帮助。

事实1 : 不同容量的存储器,访问速度差异悬殊。

磁盘(ms级别) << 内存(ns级别), 100000倍(10万倍)
若内存访问需要1s,则一次外存访问需要一天
为了避免1次外存访问,宁愿访问内存100次…所以将最常用的数据存储在最快的存储器中

事实2 : 从磁盘中读 1 B,与读写 1KB 的时间成本几乎一样

从以上数据中可以总结出一个道理,索引查询的数据主要受限于硬盘的I/O速度,查询I/O次数越少,速度越快,所以B树的结构才应需求而生;B树的每个节点的元素可以视为一次I/O读取,树的高度表示最多的I/O次数,在相同数量的总元素个数下,查询效率和树的高度密切相关,每个节点的元素个数越多,高度越低,查询所需的I/O次数越少;

B+ 树比B树更适合索引

1)B+树的磁盘读写代价更低

B+树的内部结点并没有指向关键字具体信息的指针。因此其内部结点相对B 树更小。如果把所有同一内部结点的关键字存放在同一盘块中,那么盘块所能容纳的关键字数量也越多。一次性读入内存中的需要查找的关键字也就越多。相对来说IO读写次数也就降低了;

2)B+树查询效率更加稳定

由于非终结点并不是最终指向文件内容的结点,而只是叶子结点中关键字的索引。所以任何关键字的查找必须走一条从根结点到叶子结点的路。所有关键字查询的路径长度相同,导致每一个数据的查询效率相当

3)B+树便于范围查询(最重要的原因,范围查找是数据库的常态)

B树在提高了IO性能的同时并没有解决元素遍历的我效率低下的问题,正是为了解决这个问题,B+树应用而生。

因为 B+ 树索引的所有数据均存储在叶子节点,而且数据是按照顺序排列的。B+树只需要去遍历叶子节点就可以实现整棵树的遍历。而且在数据库中基于范围的查询是非常频繁的,而 B 树由于数据分散在各个节点,因此B树不支持这样的操作或者说范围查找效率太低

补充:B树的范围查找用的是中序遍历,而B+树用的是在链表上遍历。

InnoDB与MyISAM中的B+树

InnoDBMyISAM都是MySQL的数据库引擎之一,InnoDB现为MySQL的默认存储引擎

结构区别

(1)InnoDB

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-I6wstyhG-1678887849503)(C:\Users\zhang’xia’lin\AppData\Roaming\Typora\typora-user-images\image-20230315170205607.png)]

(2)MyISAM

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QCpwZuUN-1678887849504)(C:\Users\zhang’xia’lin\AppData\Roaming\Typora\typora-user-images\image-20230315170302650.png)]

聚集索引 VS 非聚集索引

在 MySQL 中,B+ 树索引按照存储方式的不同分为聚集索引和非聚集索引。这里我们着重介绍 InnoDB 中的聚集索引和非聚集索引:

  1. 聚集索引(聚簇索引):主键索引,一个表只有一个聚簇索引,叶子节点中保存了每条记录的完整内容,保存的信息多,占用空间大

    以 InnoDB 作为存储引擎的表,表中的数据都会有一个主键,即使你不创建主键,系统也会帮你创建一个隐式的主键

    这是因为 InnoDB 是把数据存放在 B+ 树中的,而 B+ 树的键值就是主键,在 B+ 树的叶子节点中,存储了表中所有的数据。这种以主键作为 B+ 树索引的键值而构建的 B+ 树索引,我们称之为聚集索引。

  2. 非聚集索引(非聚簇索引):二级索引,一张表可以有多个非聚簇索引,叶子节点保存索引列的信息和该记录对应的主键信息,保存的信息少,占用空间小。

    以主键以外的列值作为键值构建的 B+ 树索引,我们称之为非聚集索引。

    非聚集索引与聚集索引的区别在于非聚集索引的叶子节点不存储表中的数据,而是存储该列对应的主键,想要查找数据我们还需要根据主键再去聚集索引中进行查找,这个再根据聚集索引查找数据的过程,我们称为回表查询。

明白了聚集索引和非聚集索引的定义,我们应该明白这样一句话:数据即索引,索引即数据。

聚集索引查找流程

现在假设我们要查找 id>=18 并且 id<40 的用户数据。对应的 sql 语句为:

select from user where id>=18 and id<40;

其中 id 为主键,具体的查找过程如下:

image-20230315173342640

  1. 一般根节点都是常驻内存的,也就是说页 1 已经在内存中了,此时不需要到磁盘中读取数据,直接从内存中读取即可。

    从内存中读取到页 1,要查找这个 id>=18 and id <40 或者范围值,我们首先需要找到 id=18 的键值。

    从页 1 中我们可以找到键值 18,此时我们需要根据指针 p2,定位到页 3。

  2. 要从页 3 中查找数据,我们就需要拿着 p2 指针去磁盘中进行读取页 3。

    从磁盘中读取页 3 后将页 3 放入内存中,然后进行查找,我们可以找到键值 18,然后再拿到页 3 中的指针 p1,定位到页 8。

  3. 同样的页 8 页不在内存中,我们需要再去磁盘中将页 8 读取到内存中。

    将页 8 读取到内存中后。因为页中的数据是链表进行连接的,而且键值是按照顺序存放的,此时可以根据二分查找法定位到键值 18。

    此时因为已经到数据页了,此时我们已经找到一条满足条件的数据了,就是键值 18 对应的数据。

    因为是范围查找,而且此时所有的数据又都存在叶子节点,并且是有序排列的,那么我们就可以对页 8 中的键值依次进行遍历查找并匹配满足条件的数据。

    我们可以一直找到键值为 22 的数据,然后页 8 中就没有数据了,此时我们需要拿着页 8 中的 p 指针去读取页 9 中的数据。

  4. 因为页 9 不在内存中,就又会加载页 9 到内存中,并通过和页 8 中一样的方式进行数据的查找,直到将页 12 加载到内存中,发现 41 大于 40,此时不满足条件。那么查找到此终止。

非聚集索引查找流程

一张表的结构为 student(id,name,luckNum),如果我们要找到幸运数字为 33 的用户信息:

select * from user where luckNum=33

非聚集索引的叶子节点中,不再存储所有的数据了,存储的是键值和主键。对于叶子节点中的 x-y。比如 8-2,左边的 8 表示的是索引luckNum的值,右边的 1 表示的是主键id的值。

image-20230315173817269

查找的流程跟聚集索引一样,我们最终会找到33-47,即该条记录的主键值为 47,找到主键后我们需要再到聚集索引中查找具体对应的数据信息,此时又回到了聚集索引的查找流程。

在 MyISAM 中,聚集索引和非聚集索引的叶子节点都会存储数据的文件地址。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值