📌 MySQL 的 B+ 树索引如何查询数据?
在 MySQL 的 InnoDB 存储引擎中,索引采用 B+ 树(B+ Tree) 结构。B+ 树索引的查询过程涉及 磁盘 IO、缓冲池、事务日志(redo log)和 MVCC 等多个环节。下面详细解析 MySQL 使用 B+ 树索引查找数据的全过程。
🧩 1. B+ 树索引结构
🔹 B+ 树的基本特点
-
多路平衡查找树
- 每个节点存多个 索引值(key),多个 指向子节点的指针(pointers)。
- 非叶子节点只存索引值,不存数据。
- 叶子节点存索引值 + 真实数据,并通过 双向链表 连接,方便范围查询。
-
磁盘 IO 友好
- 非叶子节点存大量索引,减少树的高度,降低查询时的磁盘 IO 次数。
-
范围查询高效
- 叶子节点的链表结构,使得 B+ 树能高效进行顺序扫描(适用于
BETWEEN
、ORDER BY
查询)。
- 叶子节点的链表结构,使得 B+ 树能高效进行顺序扫描(适用于
🔍 2. B+ 树索引的查询过程
假设有如下 users
表,并且 id
字段上有 B+ 树索引:
CREATE TABLE users (
id INT PRIMARY KEY,
name VARCHAR(100),
age INT
);
数据如下:
id | name | age |
---|---|---|
1 | Alice | 25 |
5 | Bob | 30 |
10 | Carol | 28 |
15 | David | 35 |
20 | Ellen | 40 |
B+ 树索引结构如下:
[ 5 15 ]
/ \
[1, 5] [10, 15, 20]
查询 SQL:
SELECT * FROM users WHERE id = 10;
🚀 查询流程
-
客户端发送 SQL 语句
- MySQL 解析 SQL,判断
id
字段是 主键索引(聚簇索引)。 - 进入查询优化器,选择 B+ 树索引查询。
- MySQL 解析 SQL,判断
-
在 B+ 树中查找 id = 10
- 第一层(根节点):[5, 15]
10 > 5
,但10 < 15
,沿着 5 的右指针 进入下一层。
- 第二层(叶子节点):[10, 15, 20]
- 找到 id = 10,获取完整行数据。
- 第一层(根节点):[5, 15]
-
查询缓存(Buffer Pool)
- MySQL 先从 Buffer Pool(缓冲池) 查找是否已加载该数据页:
- 如果数据页在缓存中,直接返回(避免磁盘 IO)。
- 如果数据页不在缓存中,从 磁盘 读取数据页,加载到缓存(LRU 机制)。
- MySQL 先从 Buffer Pool(缓冲池) 查找是否已加载该数据页:
-
返回查询结果
- MySQL 执行完成后,将数据返回给客户端。
🎯 3. 详细的 B+ 树磁盘 IO 过程
🧱 磁盘页(Page)
MySQL 采用 磁盘分页(Page) 方式存储数据,默认 每页 16KB。
查询 id = 10 时的磁盘 IO
- 读取根节点(索引页)
- 该页可能已缓存在 Buffer Pool 中,如果不在,执行一次 磁盘 IO 读取。
- 读取叶子节点(数据页)
- 如果数据页不在缓存,需要执行 第二次磁盘 IO 读取该页。
一般来说,一次查询只需要 1~3 次磁盘 IO(树高一般为 2~3 层)。
📝 4. B+ 树查询 vs. 全表扫描
🔹 使用索引查询
EXPLAIN SELECT * FROM users WHERE id = 10;
输出:
+----+-------------+-------+-------+---------------+---------+---------+-------+
| id | select_type | table | type | possible_keys | key | rows | Extra |
+----+-------------+-------+-------+---------------+---------+---------+-------+
| 1 | SIMPLE | users | const | PRIMARY | PRIMARY | 1 | NULL |
+----+-------------+-------+-------+---------------+---------+---------+-------+
- type = const(表示使用索引查找,性能高)
- rows = 1(只查找一条数据)
🔹 全表扫描(没有索引时)
EXPLAIN SELECT * FROM users WHERE name = 'Alice';
输出:
+----+-------------+-------+------+---------------+------+---------+-------------+
| id | select_type | table | type | possible_keys | key | rows | Extra |
+----+-------------+-------+------+---------------+------+---------+-------------+
| 1 | SIMPLE | users | ALL | NULL | NULL | 1000000 | Using where |
+----+-------------+-------+------+---------------+------+---------+-------------+
- type = ALL(表示全表扫描,性能低)
- rows = 1000000(可能遍历百万级数据)
结论: ✅ 使用 B+ 树索引能显著减少扫描行数,提高查询效率。
📌 5. 范围查询
B+ 树的 叶子节点通过链表连接,因此范围查询比 B-树更快:
SELECT * FROM users WHERE id BETWEEN 5 AND 15;
查询流程:
- 在 B+ 树中定位
id = 5
的叶子节点。 - 沿着叶子节点的链表,顺序遍历到
id = 15
,返回数据。
🔹 范围查询高效的原因
- B+ 树的叶子节点链表结构,可以高效顺序扫描。
- 磁盘预读优化,一次读取多个数据页,减少磁盘 IO。
✅ 6. 总结
🛠 MySQL B+ 树索引查询过程
- SQL 解析:优化器选择索引(如
PRIMARY
)。 - B+ 树查找:
- 从 根节点 开始查找索引值所在范围。
- 进入 叶子节点 读取完整数据行。
- Buffer Pool 缓存:
- 如果数据页已缓存,避免磁盘 IO。
- 如果不在缓存,触发 磁盘 IO 读取 数据页。
- 返回查询结果。
🚀 为什么 MySQL 选择 B+ 树?
- 磁盘 IO 友好:降低树高(一般 ≤3),减少 IO 次数。
- 范围查询高效:叶子节点链表结构,顺序读取快。
- 索引节点存储密度高:一个节点存多个索引,减少查询层数。
📌 结论
MySQL B+ 树索引使得查询速度接近 O(log N),远优于全表扫描 O(N)。通过 Buffer Pool 缓存、磁盘 IO 预读 进一步优化查询性能,确保高效的数据库查询。🚀