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

B-树与B+树

B树的引入

从磁盘查找数据效率低,一般是什么原因?

在这里插入图片描述

影响IO效率因素:读写数据越大速度越慢;读写次数越多速度越慢。

索引 ⇒\Rightarrow 更快的查询数据

如何来设计一个文件系统 k:v ⇒\Rightarrow 用什么数据结构来实现?

线性表

  • 顺序表
    • 优点:支持随机访问(O(1) 时间访问任意元素)。
    • 缺点:插入/删除效率低(平均需移动 O(n) 个元素)。
  • 链表
    • 优点:插入/删除效率高(O(1) 若已知位置)。
    • 缺点:不支持随机访问,必须从头遍历。

哈希表

  • 优点:等值查询比较快

  • 缺点:

    1. 哈希冲突后,数据散列不均匀,产生大量线性查找,性能下降;

    2. 不支持范围查询。

      -- 范围查询是指:你不需要知道某个字段的确切值,而是想查找在一个数值区间内的所有记录。
      -- 查询年龄在 20 到 30 之间的所有人
      SELECT * FROM users WHERE age BETWEEN 20 AND 30;
      

      根本原因:哈希表是无序的

      哈希表的工作原理是:哈希表通过一个哈希函数将键(key)映射为一个位置(哈希值),然后存储数据。

      例如:

      hash(25) = 9287   → 存储在第 9287 号桶
      hash(26) = 103    → 存储在第 103 号桶
      hash(27) = 5000   → 存储在第 5000 号桶
      

      虽然 25, 26, 27 是连续的数值,但它们的哈希值完全随机分布,没有任何顺序关系。

      范围查询需要“顺序性”,而哈希表没有

      要高效执行 WHERE age > 25,数据库需要:

      • 找到第一个 age > 25 的记录;
      • 然后顺序读取后续所有符合条件的记录。

      但哈希表中:

      • 数据不是按 age 排序的;
      • 无法从“25 的下一个”开始扫描;
      • 每个键值的位置是独立计算的。

      所以你无法利用哈希索引进行“区间扫描”或“顺序遍历”。

      而必须:

      1. 遍历每一条记录;
      2. 计算其 age的哈希值;
      3. 查看是否存在于哈希表中;
      4. 再判断 age > 25。

      这实际上等同于全表扫描,索引完全失效!

  • BST

    • 退化成链表:如果按顺序插入数据(如 1, 2, 3, 4, 5),BST 会退化成一个链表,查找时间复杂度从理想的 O(log n) 变成 O(n),完全丧失了索引的优势。
    • 查询效率极低:此时查找某个值需要遍历所有节点,和全表扫描无异。

    例如:插入有序数据后,BST 会变成一条斜线,查找第6个节点需要6次比较,性能与无索引相当

  • AVL

    • 严格平衡:AVL 树要求左右子树高度差不超过1,通过频繁的旋转操作(左旋、右旋)维持平衡。
    • 插入/删除成本高:每次插入或删除都可能触发 O(log n) 次旋转,尤其在写密集型场景下,性能开销极大。
    • 适合读多写少,不适合数据库:数据库需要频繁更新索引,AVL 的高维护成本使其不适用。

    虽然查询快(O(log n)),但写操作的旋转代价太高,拖累整体性能。

  • 红黑树

    • 允许一定失衡:红黑树通过颜色标记和规则放宽平衡条件,树高最多为 2log(n),比 AVL 更“宽松”,减少了旋转次数。
    • 树仍然太“瘦高”:对于海量数据(如10亿条),红黑树的深度仍可达 60层以上。
    • 磁盘IO次数太多:数据库索引通常存储在磁盘上,每次节点访问可能是一次磁盘IO。60次磁盘IO在数据库场景下是不可接受的。

    红黑树虽然写性能优于AVL,但树太高 → IO次数多 → 查询慢。


什么是B树

B-Tree

B树属于多叉树又名平衡多路查找树(查找路径不止两个),数据库索引技术里大量使用着B树和B+树的数据结构。

满足下列要求的m叉树:

  1. 树中每个结点至多有 m 个孩子结点(即至多有 m-1 个关键字)

  2. 每个结点的结构为:

    在这里插入图片描述

    其中“4阶”指的是该树中一个节点最多可以拥有4个子节点,更详细地说,“阶”(通常用 m 表示)定义了B树的分支能力。

  3. 除根结点外,其他结点至少有 m/2 个孩子结点

  4. 若根结点不是叶子结点,则根结点至少有两个孩子结点

  5. 所有叶子结点都在同一层上,即B树是所有结点的平衡因子均等于0的多路查找树


B树的查找

磁盘预读

  • 内存跟磁盘发生数据交互的时候,一般情况下有一个最小的逻辑单元,称之为页,datapage;
  • 页一般由操作系统决定是多大,一般是4K或者8K,我们在数据交互时,可以取页的整数倍来进行读取。

在这里插入图片描述

  • 第一次IO,首先加载磁盘块1, 28>16 ,28<34 ,找到磁盘块1 →\rightarrow p2
  • 第二次IO,加载磁盘块3,28>25 ,28<31 ,找到磁盘块2 →\rightarrow p2
  • 第三次IO,加载磁盘块8, 28=28 ,返回

B+树的引入

B树的缺点

  • 非叶子节点也存数据 → 树更高 → 查询慢

    假设每个磁盘页是 16KB,每个索引项(key + data)占 200 字节。

    • 每个节点最多能存:16KB / 200B ≈ 80 个 key+data
    • 如果我们改用 B+树,非叶子节点只存 key 和子节点指针(比如 key+指针 = 20B),则能存 16KB / 20B ≈ 800 个 key

    结果:I/O 更多,性能更差。

    • B树:分支少 → 树更高(比如 4 层)
    • B+树:分支多 → 树更矮(比如 3 层)
  • 不支持高效范围查询

    SELECT * FROM user WHERE id BETWEEN 1000 AND 9999;
    
    • 在 B树中怎么做?

      找到 id=1000 → 从根开始查找

      找到 id=1001 → 再从根开始查找

      ……

      找到 id=9999 → 每次都要从根遍历到叶子

      因为 叶子节点之间没有连接,无法“顺着找下一个”。

    • B+树怎么解决?

      1. 非叶子节点只存 key 和指针 → 更多分支 → 树更矮 → I/O 更少
      2. 所有数据都在叶子节点,且 叶子节点用双向链表连接
      3. 范围查询时:
        • 先查 id=1000(3次 I/O 到叶子)
        • 然后 顺着链表往后扫,直到 id=9999
        • 后续不再需要从根查找!

      一次定位 + 顺序扫描 = 极快的范围查询

  • 查询效率不稳定

    • 在 B树 中:

      • 如果你要查的 id=500,它可能正好在根节点 → 1 次 I/O 就返回
      • 但 id=900 在叶子 → 要走到底(4 次 I/O)

      不同查询耗时差异大,不稳定

    • 而在 B+树 中:

      • 所有查询都必须走到叶子节点
      • 路径长度一致 → 性能稳定可预测

B+树 ⇓\Downarrow


什么是B+树

B+树保留了B树的基本特性,但对节点数据的存储方式进行了调整,以提升磁盘I/O效率和范围查询性能。它广泛应用于数据库(如MySQL的InnoDB引擎)和文件系统中。

在这里插入图片描述

B+树的主要特点:

  1. 非叶子节点仅存储索引:B+树的非叶子节点不存储实际的数据,只存储关键字和子节点指针,起到索引的作用。这使得每个节点可以容纳更多的关键字,从而降低树的高度,减少磁盘I/O次数。

  2. 所有数据存储在叶子节点:所有实际的数据记录都存储在叶子节点中,且叶子节点包含了全部关键字的信息以及指向对应数据记录的指针。

  3. 叶子节点构成有序链表:B+树的叶子节点之间通过指针连接成一个有序的双向链表。这一特性使得范围查询(如BETWEEN、ORDER BY)变得非常高效,只需遍历链表即可。

  4. 所有叶子节点位于同一层:B+树是高度平衡的,所有叶子节点都处于相同的深度,保证了查询效率的稳定性,任何查找、插入、删除操作的时间复杂度均为O(log n)。

  5. 更适合磁盘存储:由于节点大小通常与磁盘块大小对齐,且树的高度较低,B+树能显著减少访问磁盘的次数,非常适合处理大规模数据。

  6. 查询效率稳定:查找操作必须最终到达叶子节点才能命中,因此每一次查找的路径长度基本一致,性能稳定,等价于在关键字全集上进行一次二分查找。


http://www.dtcms.com/a/319095.html

相关文章:

  • AI大模型专题:LLM大模型(初识)
  • dubbo的metadata-report是做啥的
  • 17.11 单卡24G显存微调GLM-4实战:QLoRA到全参数调优,准确率狂飙42.7%
  • Qt: WA_DontCreateNativeAncestors
  • 【缩点 拓扑序】P3119 [USACO15JAN] Grass Cownoisseur G|省选-
  • 【关于Java中==和equals( )和hashCode( )三者异同】
  • 写Rust GPU内核驱动:GPU驱动工作原理简述
  • 【性能测试】---测试工具篇
  • 医疗人效管理新标杆:盖雅工场如何赋能健康服务企业提质增效
  • 「iOS」————自动释放池底层原理
  • CSS包含块与百分比取值机制完全指南
  • 数据分析——Pandas库
  • 添加内容溢出时显示完整内容提示的功能
  • QT5.15 mingw
  • c++之 栈浅析
  • Python 数据类型及数据类型转换
  • platform总线简介和使用场景说明
  • 基于Ruby的IP池系统构建分布式爬虫架构
  • 《算法导论》第 9 章 - 中位数和顺序统计量
  • 网页图片视频一键下载+视频去重修改 ,覆盖B站等多个平台
  • 【基础知识】springboot+vue 基础框架搭建(更新中)
  • 中国MCP市场:腾讯、阿里、百度的本土化实践
  • AI绘画:生成唐初李世民全身像提示词
  • 前后端加密传数据实现方案
  • 强反光干扰下读数误差↓79%!陌讯多模态算法在仪表盘识别场景的落地优化​
  • LINUX-文件查看技巧,重定向以及内容追加,man及echo的使用
  • 迅为RK3588开发板Android proc文件系统查询-内核版本查询
  • PyTorch RNN 名字分类器
  • 11-netty基础-手写rpc-支持多序列化协议-03
  • 【MySQL基础篇】:MySQL事务并发控制原理-MVCC机制解析