为什么MySQL InnoDB选择B+树?

首先抛个问题"如何进行sql优化?"

讲到sql优化很多人第一反应:"不就是加个索引嘛?!",的确,加索引确实可以提高sql的查询效率,但为什么在编写sql进行查询的时候还是会出现索引失效导致全表扫描的情况?其实,根本原因在于你没有理解索引的底层设计------B+树数据结构。

什么是B+树?

1、普通二叉树

了解B+树之前,我们先了解一下最简单的二叉树

可以看出二叉树每个节点最多有两个子节点的树结构,子节点分为左孩子和右孩子。它本身只是一种树形结构,不保证有序。

因为不保证插入值的有序性所以查找某个值必须遍历整棵树(O(n)),后面为了保证值有序 的结构来实现高效查找就有了二叉搜索树

2、二叉搜索树(BST):

如下图所示:

每个节点最多有两个子节点的树结构,子节点分为左孩子和右孩子。

  • 对于任意节点,左子树所有节点的值 < 该节点的值

  • 右子树所有节点的值 > 该节点的值

中序遍历 (左→根→右)会得到一个升序序列:30、40、45、50、55、60、99

BST 通过值有序实现了快速查找,但普通BST在极端情况下会退化成链表(如顺序插入 1,2,3,4,5),查找又变成 O(n)。

于是又出现了平衡二叉树(AVL、红黑树),在保持值有序的同时,通过旋转维持树的高度平衡,保证 O(log n) 的操作效率

3、 平衡二叉树

  • AVL树:严格平衡,左右子树高度差不超过1,查找快,但插入删除旋转多。

  • 红黑树:近似平衡,插入删除效率高,广泛用于内存数据结构(如Java TreeMap)。

如果左右子树超过1,那么就会通过旋转保证树的平衡,如图在插入88时正常情况下是插入到70这个节点的右边但是会导致66节点的左右子树高度超过1,所以会旋转一下节点66和节点88作为70的子节点

主要是看某个子树的左边和右边的子节点有没有大于1,如果超过了1,比如66这个节点的子树,左边是0个子节点,右边是2个子节点>1所以就需要找个平衡节点70进行左旋转

问题:每个节点最多两个子节点,树的高度较高。当数据量很大时,树会变得很高,导致磁盘I/O次数多。对于数据库这种以磁盘为存储介质的场景,二叉树不适合,所以就有了下面的B树数据结构。

4、B树(B-Tree)

B树是一种多路平衡查找树,每个节点可以拥有多于两个的子节点。它被设计用于磁盘等外存储设备,降低树的高度,减少磁盘I/O。

如上图所示,每个节点最多有 3个子节点,所以每个节点最多有 2 个关键字

结构特点

  • 每个节点最多有 m 个子节点(m称为阶)。

  • 每个节点最多有 m-1 个关键字。

  • 根节点至少有2个子节点(除非是叶子)。

  • 非根非叶节点至少有 ⌈m/2⌉ 个子节点,即至少 ⌈m/2⌉-1 个关键字。

  • 所有叶子节点都在同一层,树是完全平衡的。

  • 每个节点都存储数据(关键字和对应的数据记录或指针)。

树高度低,磁盘I/O少。节点大小通常设计为一个磁盘页(如16KB),一次I/O读取一个节点,效率高。但是每个节点都存储数据(关键字/索引和值),导致节点能容纳的关键字数量减少,树可能略高。范围查询需要中序遍历,跨层跳转,效率一般。

5、B+树(B+ Tree)

B+树是B树的变体,专门为数据库和文件系统优化。核心区别:非叶子节点只存储键(索引),不存储数据;所有数据都存储在叶子节点,且叶子节点之间用链表连接。

结构特点

  • 非叶子节点仅包含键值和指向子节点的指针,不包含数据记录。

  • 所有数据记录都存储在叶子节点。

  • 叶子节点包含所有键值及对应的数据(或数据指针)。

  • 叶子节点之间通过双向链表连接,支持高效范围查询。

  • 所有叶子节点在同一层,树完全平衡。

  • 每个节点(页)能容纳更多的键,因为非叶子节点不存数据,所以树更矮。

对比项 二叉树(BST/平衡树) B树 B+树
子节点数 最多2个 最多m个 最多m个
数据存储位置 每个节点都存数据 每个节点都存数据 仅叶子节点存数据,非叶子只存键
叶子节点链接 双向链表连接
树的高度 较高(log₂n) 较低(logₘn) 更低(非叶子不存数据,可存更多键)
查找路径 可能提前命中 可能提前命中 必须到叶子
范围查询 中序遍历,效率低 中序遍历,跨层跳转 叶子链表顺序扫描,效率极高
磁盘I/O 高(节点小,树高) 较低 最低(树矮,节点大)
适用场景 内存数据结构 文件系统、部分数据库 数据库索引(MySQL InnoDB)、文件系统
平衡性 需额外平衡(AVL/红黑) 自平衡 自平衡

总结:为什么选择B+树

  1. 减少磁盘I/O:非叶子节点不存数据,每个页能容纳更多键,树更矮。通常3~4层就能存储上亿条记录,查找只需3~4次I/O。

  2. 范围查询高效 :叶子节点链表使得BETWEEN><ORDER BY等操作可以顺序扫描。

  3. 全表扫描快:只需遍历叶子链表,无需遍历整棵树。

  4. 查询性能稳定:每次查找都走到叶子,路径长度相同,性能可预测。

  5. 更适合磁盘预读:节点大小与磁盘页对齐,一次I/O读取一个节点,预读相邻页。

相关推荐
AI 思录1 小时前
Prompt 事故档案(八):日常表达被标为“待校准”,AI 的爹味语法从哪里来
大数据·人工智能·算法·prompt·用户体验·ai合规
月光船幽幽1 小时前
跨范式映射的稳定接口设计
人工智能·python·算法
瀚高PG实验室2 小时前
使用pg_stat_statements抓取数据库TOP SQL
数据库·sql·postgresql·瀚高数据库
郝学胜-神的一滴3 小时前
C++11 工程级应用 09:告别无谓拷贝,解锁高性能移动语义
开发语言·数据结构·c++·vscode·软件工程·visual studio
2601_965742223 小时前
全媒体运营与短视频代运营,两者有什么区别?
大数据·数据结构·人工智能·算法·ai·媒体
yanwumuxi3 小时前
【数据库系列】opensearch数据备份和恢复
数据库
anxiao_m3 小时前
2026企业AI数字孪生选型攻略,不同场景对应不同解决方案
大数据·网络·数据库
SelectDB3 小时前
从 ClickHouse 迁移到 Doris:SQL 兼容、同步与验证清单(实战笔记)
大数据·数据库·数据分析
wordbaby4 小时前
混合检索:两全其美的艺术
人工智能·算法