B+ 树是 MySQL(InnoDB)以及绝大多数关系型数据库采用的索引数据结构。理解 B+ 树,就能理解数据库性能的底层逻辑。
我用表格 + 图示的方式,把 B+ 树的核心知识点系统梳理出来。
一、为什么数据库要用 B+ 树?
| 数据结构 | 查询复杂度 | 磁盘 I/O 次数 | 适用场景 | 数据库是否采用 |
|---|---|---|---|---|
| 有序数组 | O(log n)(二分查找) | 很少(内存友好) | 静态数据 | ❌ 数据插入成本太高 |
| 链表 | O(n) | 多 | 简单队列 | ❌ 太慢 |
| AVL/红黑树 | O(log n) | ~log n(可能有几十次) | 内存数据结构 | ❌ 树太高,磁盘 I/O 多 |
| B 树 | O(log n) | 较少(节点大) | 文件系统、部分数据库 | ⚠️ 范围查询性能不如 B+ 树 |
| B+ 树 | O(log n) | 最少(节点大且矮) | 绝大多数关系型数据库 | ✅ 最佳选择 |
| 哈希表 | O(1)(等值查询) | 1 次 | 等值查询场景 | ✅ 用于 Memory 引擎或二级索引 |
核心结论
B+ 树的优势在于 矮(树层数少)+ 平(节点数据量大),磁盘 I/O 次数最少,最适合磁盘存储的数据库。
二、B+ 树的核心结构特征
| 特征 | B 树(平衡多路搜索树) | B+ 树(B-树的变体) | 为什么 B+ 树更好 |
|---|---|---|---|
| 数据存储位置 | 所有节点(根、枝、叶)都存数据 | 只有叶子节点存数据 | 非叶子节点可以存放更多索引,降低树高 |
| 叶子节点关系 | 叶子节点独立 | 所有叶子节点通过双向链表相连 | 范围查询无需回溯父节点,效率极高 |
| 冗余存储 | 不会冗余存储 | 父节点冗余存储子节点最小键值 | 便于快速定位数据 |
| 叶子节点数据 | 存完整行数据 | 存主键 + 行指针 | InnoDB 中二级索引存主键值 |
图示对比(简化)
B 树结构:
[20 | 数据]
/ | \
[10 | 数据] [30 | 数据] [40 | 数据]
B+ 树结构:
[20] [40]
/ | \
[10][20] [30][40] [40][50]
↓ ↓ ↓
链表连接 →→→→→→→→→→
三、B+ 树的三个核心优势
| 优势 | 说明 |
|---|---|
| 磁盘 I/O 最少 | B+ 树节点大(通常 16KB),一次 I/O 能加载大量索引;树层数低(通常 3-4 层),查询只需 3-4 次 I/O |
| 范围查询极快 | 找到下限值后,沿叶子节点链表向前遍历即可,不需要回溯父节点 |
| 查询性能稳定 | 所有查询最终都会到达叶子节点,遍历路径长度总是固定的(从根到叶) |
这意味着: 查询 1 行和查询 1000 行的路径长度相同,时间复杂度总是 O(log n)。
四、InnoDB 的 B+ 树与数据存储
InnoDB 中,B+ 树用于两种主要索引:
| 索引类型 | B+ 树叶子节点存储内容 | 说明 |
|---|---|---|
| 聚簇索引(主键索引) | 存储完整行数据 | 表数据本身就是一颗 B+ 树,以主键作为索引键 |
| 二级索引(非主键索引) | 存储主键值 | 查询时先找到主键,再回表查询完整数据(称为"回表") |
InnoDB B+ 树结构示意
聚簇索引 B+ 树
[主键1] [主键2] [主键3]
| | |
叶子节点:存储完整行数据
↓ ↓ ↓
(id=1, name=张三, age=25)
(id=2, name=李四, age=30)
(id=3, name=王五, age=28)
二级索引 B+ 树
[姓名] [姓名] [姓名]
| | |
叶子节点:存储对应主键值
↓ ↓ ↓
(张三→id=1) (李四→id=2) (王五→id=3)
五、为什么 3 层 B+ 树能支撑 2000 万行数据
以 InnoDB 默认 page_size = 16KB 为例:
| 层级 | 作用 | 存储内容 | 数量 |
|---|---|---|---|
| 第 1 层(根节点) | 指向子节点的指针 | 约 1200 个键值对 | 1 个节点 |
| 第 2 层(枝节点) | 指向叶子节点的指针 | 约 1200 个键值对 | 1200 个节点 |
| 第 3 层(叶子节点) | 存储数据行 | 约 16 行数据/页 | 1200 × 1200 = 1,440,000 个节点 |
关键推导
非叶子节点可存约 1200 个 索引键值(每行约 13 字节)
叶子节点可存约 16 条 完整行数据(假设每行约 1KB)
3 层 B+ 树最多可存储:
1200(第1层)× 1200(第2层)× 16(叶子节点数据行数)= 23,040,000 行
所以,3 层 B+ 树大约支撑 2000 万行数据 ,查询需要 3 次磁盘 I/O。
六、B+ 树 vs B 树 vs LSM 树
| 对比维度 | B+ 树 | B 树 | LSM 树 |
|---|---|---|---|
| 写性能 | 一般(需随机写入) | 一般 | 高(顺序写入,先写内存) |
| 读性能 | 高(稳定 O(log n)) | 较高 | 一般(需合并多个 SSTable) |
| 范围查询 | 极高(叶子节点链表) | 中等(需回溯父节点) | 一般(需扫描多个文件) |
| 事务支持 | ✅ 支持 | ✅ 支持 | ⚠️ 较弱 |
| 适用数据库 | MySQL、PostgreSQL、Oracle | 部分文件系统 | Cassandra、HBase、LevelDB、RocksDB |
| 适用场景 | OLTP(在线交易系统) | 文件系统索引 | OLAP、日志存储、写入密集型 |
七、B+ 树知识速查表
| 问题 | 答案 |
|---|---|
| B+ 树是什么? | 一种平衡多路搜索树,数据库索引的标准结构 |
| 和 B 树的主要区别? | B+ 树只在叶子节点存数据,B 树所有节点都存数据 |
| 为什么不用二叉树? | 二叉树太高,磁盘 I/O 多,B+ 树矮而平 |
| 叶子节点的作用? | 存储全部数据(聚簇索引)或主键值(二级索引),并通过链表连接 |
| 范围查询为什么快? | 找到下限后,沿叶子节点链表遍历即可 |
| 一个节点通常多大? | 16KB(MySQL InnoDB 默认页大小) |
| 查询需要几次磁盘 I/O? | 3-4 次(树高对应 I/O 次数) |
| 什么时候用到 B+ 树? | 执行 SELECT 语句时,数据库自动使用 B+ 树索引加速查询 |
八、B+ 树在数据库中的实际应用
| 场景 | B+ 树的作用 |
|---|---|
| 主键等值查询 | SELECT * FROM users WHERE id = 100 → 走聚簇索引,3 次 I/O |
| 主键范围查询 | SELECT * FROM users WHERE id BETWEEN 1 AND 100 → 定位起点后沿叶子链表遍历 |
| 二级索引查询 | SELECT * FROM users WHERE name = '张三' → 先走二级索引找到主键,再回表 |
| ORDER BY | SELECT * FROM users ORDER BY id → 直接遍历叶子节点链表,无需额外排序 |
| COUNT(*) | 叶子节点链表快速统计行数(非精确) |
九、一句话总结
B+ 树是数据库索引的灵魂。它通过矮平的树形结构 + 叶子节点链表,用最少的磁盘 I/O 实现了最稳定的查询性能。
如果理解了 B+ 树,你对数据库索引设计、查询优化、分库分表等高级话题的理解都会更加透彻。