当前位置: 首页 > 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.lryc.cn/news/612972.html

相关文章:

  • 动力电池点焊机:效率质量双提升,驱动新能源制造升级
  • Dify 从入门到精通(第 20/100 篇):Dify 的自动化测试与 CI/CD
  • Oracle exp imp expdp impdp 命令详解
  • PCB制造中压接孔、插接孔、沉头孔、台阶孔的区别及生产流程
  • 《C语言》函数练习题--1
  • 基于大数据的美食视频播放数据可视化系统 Python+Django+Vue.js
  • Vscode Data Wrangler 数据查看和处理工具
  • GitHub 上 Star 数量前 20 的开源 AI 项目
  • 中国MCP市场:腾讯、阿里、百度的本土化实践
  • 医疗人效管理新标杆:盖雅工场如何赋能健康服务企业提质增效
  • Java 大视界 -- Java 大数据在智能教育在线课程互动优化与学习体验提升中的应用(386)
  • 一篇文章用大白话带初学者搞清训练集、测试集及验证集关系及场景逻辑(包清楚)
  • LLMs api价格对比平台
  • --- Eureka 服务注册发现 ---
  • 【第7话:相机模型3】自动驾驶IPM图像投影拼接技术详解及代码示例
  • TikTok Shop冷启动破局战:亚矩阵云手机打造爆款账号矩阵
  • AWS RDS自定义终端节点深度分析工具:Python脚本详解
  • 手机控制断路器:智能家居安全用电的新篇章
  • STM32HAL 快速入门(一):点灯前的准备 —— 从软件安装到硬件原理
  • 云手机存在的意义是什么?
  • 数字取证:可以恢复手机上被覆盖的数据吗?
  • 【macOS操作系统部署开源DeepSeek大模型,搭建Agent平台,构建私有化RAG知识库完整流程】
  • 如何提高云手机中数据信息的安全性?
  • Git Status 命令深度指南:洞悉仓库状态的核心艺术
  • Flutter开发 Slider组件(如音量控制)
  • C语言strncmp函数详解:安全比较字符串的实用工具
  • 使用Cloud Document Converter将飞书文档导出为markdown
  • Android渲染/合成底层原理详解
  • MySQL GROUP BY 语句详细说明
  • 《算法导论》第 9 章 - 中位数和顺序统计量