当前位置: 首页 > news >正文

MySQL的底层数据结构:B+树

为了更好的理解B+树,先介绍下B树增删查的原理,然后在B树的基础上理解B+树,并对比B树为何B+树更适合实现MySQL

所谓m阶B树是所有结点的平衡因子均等于0的m路平衡查找树。一棵m阶B树或为空树,或为满足如下特性的m又树:1)树中每个结点至多有m棵子树,即至多有m-1个关键字。2)若根结点不是叶结点,则至少有2棵子树,即至少有十个关键字。3)除根结点外的所有非叶结点至少有[m/2]棵子树,即至少有[m/2]-1个关键字。4)所有非叶结点的结构如下:

其中,K(i=1,2,…,n)为结点的关键字,且满足K<K<…<K;P(i=0.1.…,n)为指向子树根结点的指针,且指针P所指子树中所有结点的关键字均小于K,P,所指子树中所有结点的关键字均大于K;n([m/2]-1≤n≤m-1)为结点中关键字的个数。5)所有的叶结点"都出现在同一层次上,并且不带信息(可以视为外部结点或类似于折半查找判定树的失败结点,实际上这些结点并不存在,指向这些结点的指针为空)。为一棵5阶B树,可以借助该实例来分析上述性质:

1)结点的孩子个数等于该结点中关键字个数加1。2)若根结点没有关键字就没有子树,则此时B树为空;若根结点有关键字,则其子树个数必然大于或等于2,因为子树个数等于关键字个数加1。3)除根结点外的所有非叶结点至少有[m/2]=[5/2]=3棵子树(即至少有【m/2]-1=[5/2]-1=2个关键字):至多有5棵子树(即至多有4个关键字)4)结点中的关键字从左到右递增有序,关键字西侧均有指向子树的指针,左侧指针所指子树的所有关键字均小于该天键字,右侧指针所指子树的所有关键字均大于该关键字,者看成下层结点的关键字总是落在由上层结点的关键字所划分的区间内,如第二层最东结点的关键字划分成了3个区间:(-∞,5),(5,11),(11,+∞),该结点中的3个指针所指子树的关键字均分别落在这3个区间内。5)所有叶结点均在第4层,代表查找失败的位置。

在B树上进行查找与二叉排序树很相似,只是每个结点都是多个关键字的有序表,在每个结点上所做的不是两路分支决定,而是根据该结点的子树所做的多路分支决定。
B树的查找包含两个基本操作:①在B树中找结点:②在结点内找关键字。由于B树常储在磁盘上,则前一查找操作是在磁盘上进行的,而后一查找操作是在内存中进行的,即在磁上找到目标结点后,先将结点信息读入内存,然后再采用顺序查找法或折半查找法。因此,在盘上进行查找的次数即目标结点在B树上的层次数,决定了B树的查找效率。
在B树上查找到某个结点后,先在有序表中进行查找,若找到则查找成功,否则按照对应的指针信息到所指的子树中去查找(例如,在图7.28中查找关键字42,首先从根结点开始,根结点只有一个关键字,且 42>22,若存在,必在关键字22的右边子树上,右孩子结点有两个关键字,而36<42<45,则若存在,必在36和45 中间的子树上,在该子结点中查到关键字 42,查找成功)。查找到叶结点时(对应指针为空),则说明树中没有对应的关键字,查找失败。

B树的高度(磁盘存取次数):B树中的大部分操作所需的磁盘存取次数与B树的高度成正比。下面来分析B树在不同情况下的高度。当然,首先应该明确B树的高度不包括最后的不带任何信息的叶结点所处的那一层(也有对B树的高度的定义中,包含最后的那一层)。若n≥1,则对任意一棵包含n个关键字、高度为h、阶数为m的B树:1)若让每个结点中的关键字个数达到最多,则容纳同样多关键字的B树的高度达到最小。因为B树中每个结点最多有m棵子树,m-1个关键字,所以在一棵高度为h的m阶B树中关键字的个数应满足n≤(m-1)(1+m+m^2+…+m^(k-1))=m^k-1,因此有h≥log(n+1)
2)若让每个结点中的关键字个数达到最少,则容纳同样多关键字的B树的高度达到最大。第一层至少有1个结点;第二层至少有2个结点;除根结点外的每个非叶结点至少有[m/2]棵子树,则第三层至少有2[m/2]个结点……第h+1层至少有2([m/2])^(h-1)个结点,注意到第n+1层是不包含任何信息的叶结点。对于关键字个数为n的B树,叶结点即查找不成功的结点为n+1,由此有n+1>2([m/2]),即h<log[m1((n+1)/2)+1。例如,假设一棵3阶B树共有8个关键字,则其高度范围为2≤h<3.17,取整数

与二叉排序树的插入操作相比,B树的插入操作要复杂得多。在B树中查找到插入的位置后并不能简单地将其添加到终端结点(最底层的非叶结点)中,因为此时可能会导致整棵树不再足B树定义中的要求。将关键字key插入B树的过程如下:

1)定位,利用前述的B树查找算法,找出插入该关键字的终端结点(在B树中查找key时会找到表示查找失败的叶结点,因此插入位置一定是最底层的非叶结点)。2)插入,每个非根结点的关键字个数都在[[m/2]-1,m-1]。若结点插入后的关键字个数小于m,可以直接插入;若结点插入后的关键字个数大于m-1,必须对结点进行分裂。
分裂的方法是:取一个新结点,在插入key后的原结点,从中间位置(m2])将其中的关键字分为两部分,左部分包含的关键字放在原结点中,右部分包含的关键字放到新结点中,中间位置m/2])的结点插入原结点的父结点。若此时导致其父结点的关键字个数也超过了上限,则维续进行这种分裂操作,直至这个过程传到根结点为止,进而导致B树高度增1;对于m=3的B树,所有结点中最多有m-1=2个关键字,若某结点中已有两个关键字,则结点已满,如图所示,插入一个关键字60后,结点内的关键字个数超过了 m-1,如图(b)所示,此时必须进行结点分裂,分裂的结果如图(c)所示

B树的剂除操作与插入操作类似,但要稍微复杂一些,即要使得删除后的结点中的关键字个数>=[m/2]-1,因此将涉及结点的“合并”问题。当被删关键字k不在终端结点中时,可以用k的前驱(或后继)K,即k的左侧子树中“最右下”的元素(或右侧子树中“最左下”的元素),来替代k,然后在相应的结点中删除k,关键字必定落在某个终端结点中,则转换成了被删关键字在终端结点中的情形。在图中的4阶B树中,删除关键字80,用其前驱78替代,然后在终端结点中删除78。

因此只需讨论被删关键字在终端结点中的情形,有下列三种情况:1)直接删除关键字。若被删关键字所在结点删除前的关键字个数>[m/2],表明删除该关键字后仍满足B树的定义,则直接删去该关键字。
2)兄弟够借。若被删关键字所在结点删除前的关键字个数=[m/2]-1,且与该结点相邻的右(或左)兄弟结点的关键字个数>=[m/2],则需要调整该结点、右(或左)兄弟结点及其双杀结点(父子换位法),以达到新的平衡。在图(a)中删除4阶B树的关键字65、右兄弟关键字个数>=[m/2]=2,将71取代原65的位置,将74调整到 71的位置。

3)兄弟不够借。若被删关键字所在结点删除前的关键字个数=[m/21-1,且此时与该结点相邻的左、右兄弟结点的关键字个数都=[m/2]-1,则将关键字删除后与左(或右)兄弟终点及双亲结点中的关键字进行合并。在图(b)中删除4阶B树的关键字5,它及其本兄弟结点的关键字个数=[m/2]-1=1,所以在5删除后将60合并到65 结点中。

在合并过程中,双亲结点中的关键字个数会减1。若其双亲结点是根结点且关键字个数减少至0(根结点关键字个数为1时,有2棵子树),则直接将根结点删除,合并后的新结点成为根:若双亲结点不是根结点,且关键字个数减少到[m/2]-2,则又要与它自己的兄弟结点进行调整或合并操作,并重复上述步骤,直至符合B树的要求为止。

B+树是应数据库所需而出现的一种B树的变形树。一棵m 阶 B+树应满足下列条件:1)每个分支结点最多有m棵子树(孩子结点)。2)非叶根结点至少有两棵子树,其他每个分支结点至少有m/2]棵子树。3)结点的工树个数与关键字个数相等。4)所有叶结点包含全部关键字及指向相应记录的指针,结点中将关键字按大小顺序排列并且相邻叶结点按大小顺序相互链接起来(支持顺序查找)。5)所有分支结点(可视为索引的索引)中仅包含它的各个子结点(即下一级的索引块)中关键字的最大值及指向其子结点的指针。

B+树和B树的主要差异如下:
在 B+树中,具有"个关键字的结点只含有n棵子树,即每个关键字对应一棵子树;而在B树中,具有n个关键字的结点含有n+1棵子树。 B+树每个结点(非根内部结点)的关键字个数n的范围是[m/2]<n≤m(非叶根结点:2≤n≤m);而在B树中,每个结点(非根内部结点)的关键字个数n的范围是[m/2]-1Sn≤m-1(根结点:1≤n≤m-1)。叶子节点构成有序链表:叶子节点之间通过双向指针(在InnoDB中)相互连接,形成一个天然的、支持双向遍历的有序序列。

正是因为有了这些特性,B+树非常适合用于对海量数据的管理,MySQL和Oracle等数据库就是使用了这个数据结构。

数据库的数据和索引通常存储在磁盘上,而磁盘I/O的耗时是内存操作的上万倍,是系统性能的主要瓶颈。查询过程的本质,就是不断地进行磁盘I/O来读取数据页(Page,InnoDB默认为16KB)。B+树的“多路”特性,意味着每个节点可以拥有成百上千个子节点(阶数m非常大)。这使得整棵树的结构呈现出“矮而胖”的形态,高度极低。我们来做一个简单的估算:假设主键是BIGINT类型(8字节),指针大小在InnoDB中是6字节,那么一个16KB的数据页作为非叶子节点,大概可以存放 16KB / (8B + 6B) ≈ 1170 个(关键字+指针)组合。如果树的高度为3,那么它可以索引的数据量大约是 1170 * 1170 * (叶子节点可容纳的行数)。假设一行数据为1KB,叶子节点能放16行,那么总共可以索引 1170 * 1170 * 16 ≈ 2190万 条数据。查询这2000多万条数据中的任意一条,最多只需要3次磁盘I/O。相比之下,传统的二叉树,在存储同样量级数据时,会形成一个“高瘦”的结构,查询深度巨大,将导致灾难性的磁盘I/O次数。

由于非叶子节点不存储真实数据,只存储关键字和指针,使得它们占用的空间非常小。这意味着,在系统内存允许的情况下,我们通常可以将索引树的非叶子节点(也就是那几层“目录索引”)完整地加载到内存中,并长期驻留。当一个查询请求到来时,大部分的索引寻址过程(从根节点到叶子节点的上层路径)都在飞快的内存中完成,只有最后需要读取真实数据时,才需要进行那一次或几次不可避免的磁盘I/O来访问叶子节点。这进一步将磁盘I/O的次数压缩到了极致。

接下来我们将重点分析索引分类。

1.聚簇索引:其叶子节点直接存储了完整的数据行。在InnoDB中,数据表本身就是按主键组织的一棵B+树,这棵树就是聚簇索引。因此,一张表只能有一个聚簇索引。非聚簇索引:也常被称为二级索引或辅助索引。它的叶子节点存储的不是完整数据行,而是对应行的主键值。除了主键索引外的其他索引,如普通索引、唯一索引等,都是非聚簇索引。

上面说了主要的索引分类,就不得不提一个非聚簇索引的性能问题了。那就是回表查询。当我们使用非聚簇索引进行查询时,如果查询所需的数据列不完全包含在该索引中,数据库就会经历一个两步走的过程:1)索引查找:首先在非聚簇索引(某个非主键字段建立的索引树)树上查找到满足条件的记录,并从中获取到主键值。2)主键查找(回表):再用这个获取到的主键值,去聚簇索引树(按主键id排序的树)上进行一次查找,最终定位到完整的行数据。

例如,我们有一个用户表employees,主键是id,在employee_no(员工编号)上建了一个非聚簇索引。当我们执行 SELECT * FROM employees WHERE employee_no = '86'; 时,就需要先通过employee_no索引找到主键id(比如是50),然后再根据id=50去聚簇索引中找到完整的员工信息。这个拿着主键再去查一次的过程,就是“回表”。它至少会增加一次额外的树查找和磁盘I/O,在高并发场景下,性能损耗也是不小的

2 覆盖索引:如何避免回表操作带来的性能损耗呢?答案就是 盖索引。覆盖索引并不是一种独立的索引类型,而是指在一个查询中,索引本身已经覆盖了所有需要查询的字段,因此数据库引擎无需再回到主表(聚簇索引)去获取数据的一种状态。还是上面的例子,如果我们为employees表建立一个 (employee_no, name) 的联合索引。现在,我们的查询变为 SELECT employee_no, name FROM employees WHERE employee_no = '86';。此时,查询所需的所有列(employee_no 和 name)的值,都已存在于这个联合索引的叶子节点上。数据库可以直接从该索引的叶子节点提取数据并返回,完全避免了回表操作,性能大幅提升。

B+tree:B树的所有节点都是存储数据的,B+树是B树的扩展或者变种,B+树的内节点不存储数据,只做索引,所有的数据都存储在叶子节点。此外,B+树适合范围查阅是由链表性质决定的。B+树更适合做磁盘索引,性能优于B树;因为B+树的内结点不存储数据。同样的内存空间,B树的结点除了要存储key值,还要存储value值,所以B树的节点会比B+树的节点内存占用大,从而存储B树的节点会少于B+树的节点。B树和B+树在使用场景上的差异说明:举个例子,假设有一个很大量的数据需要存储(比如100万个节点),内存上肯定无法全部存储,必然有很大部分在磁盘上。如果使用B树进行存储,由于每个节点都存储数据,必然有一部分节点存储在内存中,一部分节点存储在磁盘上。如果使用B+树存储,就有些不一样,由于B+树的内节点不存储具体数据,只做索引,所以B+树存储在内存中的节点数量会比B树多得多。所以,B+树做索引会更好,因为可以把所有的索引关系存储到内存中,然后通过一次性寻址找到存储具体数据的叶子节点。B树就无法做到这样,它只能一个节点一个节点的磁盘寻址。B树和B+树都可以做索引,但是B+树更常用于做索引,特别是索引磁盘数据。比如MySQL、mongodb、PostgreSql等数据库的索引使用的就是B+树。

当然,也不是说B树就不适合做数据库的数据结构,只是B+树会在磁盘访问和范围查询会比B+树更为劣势,但总的说来B树依旧是一个比较适合管理数据库的数据结构。

B/B+树在存储效率、范围查询效率、磁盘I/O优化、顺序访问性能以及分裂和合并操作效率等方面具有优势。这使得B/B+树成为在磁盘或其他多级存储介质上管理和组织大规模数据的一种重要的数据结构。B/B+树的节点可以存储多个键和对应的值,相比红黑树,每个节点能够容纳更多的数据。这样就减少了节点的数量,降低了存储空间的开销。B/B+树的内部节点通过键值范围进行连接,并且叶子节点通过链表连接在一起。这种结构的特点使得范围查询操作非常高效。只需遍历相应的叶子节点链表,而不需要像红黑树一样对整棵树进行中序遍历。B/B+树常用于在磁盘或其他多级存储介质上组织和管理大规模数据。B/B+树的分层结构使得在查找数据时只需进行少量的磁盘I/O操作,大大提高了访问速度。B/B+树中的键是按顺序存储的,这使得对数据的顺序访问效率非常高。对于需要顺序访问或顺序扫描大量数据的场景,B/B+树是一个很好的选择。


文章转载自:

http://kUhddBHE.ptsLx.cn
http://2WCRhNgI.ptsLx.cn
http://Sc4yEO18.ptsLx.cn
http://2ZEmnPjX.ptsLx.cn
http://X6JyOu7o.ptsLx.cn
http://EG9IzQxC.ptsLx.cn
http://tefv78AI.ptsLx.cn
http://8xhjOYGG.ptsLx.cn
http://vKgSJoyF.ptsLx.cn
http://62s1rtrV.ptsLx.cn
http://b1x9vS9T.ptsLx.cn
http://NVv0jIhB.ptsLx.cn
http://bg8Sa4yn.ptsLx.cn
http://brmrhijS.ptsLx.cn
http://as5LsodT.ptsLx.cn
http://iOSUUVO2.ptsLx.cn
http://I1yhYc7x.ptsLx.cn
http://9XBJ56qF.ptsLx.cn
http://1TCXePuG.ptsLx.cn
http://l7oAMGsL.ptsLx.cn
http://Y0iRWpgu.ptsLx.cn
http://uqbsK4Gv.ptsLx.cn
http://YrmVPAL6.ptsLx.cn
http://zWlhcYOe.ptsLx.cn
http://BgCIBiau.ptsLx.cn
http://fp5zxV49.ptsLx.cn
http://XKQqQMfQ.ptsLx.cn
http://PY9x0raq.ptsLx.cn
http://Cn2Vh0CS.ptsLx.cn
http://KrM5qpxq.ptsLx.cn
http://www.dtcms.com/a/386311.html

相关文章:

  • 【Linux】LRU缓存(C++模拟实现)
  • 冲击成本敏感度曲线驱动的拆单频率参数动态调优机制
  • Typera+Gitee+PicGo 配置markdown专用图床
  • 正则化:机器学习泛化能力的守护神
  • GCKontrol对嵌入式设备FPGA设计流程的高效优化
  • vue2+vue3-自定义指令
  • Vue基础知识点(接上篇案例)
  • 动物排队+分手厨房?合作模拟《Pao Pao》登录steam
  • 易境通货代系统:如何实现全流程自动化报关管理?
  • OpenCV:答题卡识别
  • leetcode HOT100 个人理解及解析
  • 深入落地“人工智能+”,如何构建安全、高效的算力基础设施?
  • 无人出租车(Robotaxi)还有哪些技术瓶颈?
  • 安全开发生命周期管理
  • 用住宿楼模型彻底理解Kubernetes架构(运行原理视角)
  • 【大模型】minimind2 1: ubuntu24.04安装部署 web demo
  • 扩散模型之(八)Rectified Flow
  • Facebook主页变现功能被封?跨境玩家该如何申诉和预防
  • 《Java接入支付宝沙箱支付全流程详解》
  • DevOps实战(8) - 使用Arbess+GitLab+PostIn实现Go项目自动化部署
  • 趣味学RUST基础篇(高级特征)
  • 随机森林(Random Forest)学习笔记
  • css之Flex响应式多列布局,根据容器宽度自动调整显示2列或3列布局,支持多行排列
  • HTML应用指南:利用POST请求获取全国中石化易捷门店位置信息
  • PDF24 Creator:免费全能的PDF处理工具
  • 小程序交互与一些技术总结
  • Spring Cloud - 面试知识点(负载均衡)
  • 易特ERP软件局域网版安装教程
  • qt QBoxSet详解
  • 电脑散热风扇有噪音怎么解决