为什么使用 B+树
🔴 困难题目描述
解释为什么 MySQL InnoDB 使用 B+树作为索引结构,而不是 B 树、二叉树或 Hash 表?
提示
- 考虑磁盘 I/O 特性
- 考虑范围查询场景
- 考虑数据存储效率
- 考虑表扫描需求
解法
参考答案 (3 个标签)
B+树 索引结构 磁盘IO
B+树结构特点
B+树示例(3阶):
[10 | 20 | 30]
/ | | \
/-------/ | | \-------\
/ | | \
[1,2,3,4,5] [11,12,13] [21,22,23] [31,32,33]
特点:
1. 非叶子节点只存储索引(关键字)
2. 所有数据存储在叶子节点
3. 叶子节点之间通过指针连接(双向链表)
4. 每个节点可以存储多个关键字为什么不用 B 树
| 特性 | B 树 | B+树 | MySQL 选择 |
|---|---|---|---|
| 数据存储 | 所有节点 | 仅叶子节点 | ✓ B+树 |
| 查询稳定性 | 不稳定(根到叶) | 稳定(都在叶子) | ✓ B+树 |
| 范围查询 | 需要中序遍历 | 叶子节点顺序访问 | ✓ B+树 |
| 磁盘 I/O | 较多 | 较少(非叶子节点小) | ✓ B+树 |
B 树的问题:
- 非叶子节点存储数据,节点大小受限,树的高度增加
- 范围查询需要在中序遍历中跳转,效率低
为什么不用二叉树
| 特性 | 二叉树 | B+树 |
|---|---|---|
| 树高度 | O(log₂ n) | O(log_m n) |
| 1000万数据 | ~24 层 | ~3 层 |
| 磁盘 I/O | 多次 | 极少 |
示例:
二叉树:1000万数据
log₂(10,000,000) ≈ 24 次磁盘 I/O
B+树:1000万数据,阶数 m=1000
log_1000(10,000,000) ≈ 3 次磁盘 I/O为什么不用 Hash
| 特性 | Hash | B+树 |
|---|---|---|
| 等值查询 | O(1) ✓ | O(log n) |
| 范围查询 | 不支持 ✗ | ✓ |
| 顺序访问 | 不支持 ✗ | ✓ |
| LIKE 查询 | 不支持 ✗ | 支持前缀 |
Hash 的问题:
- 不支持范围查询(
WHERE age > 18) - 不支持排序(
ORDER BY) - 不支持模糊查询(
LIKE 'abc%')
B+树的具体优势
1. 减少磁盘 I/O
// 假设:
// - 磁盘页大小:16KB
// - 每个索引项:8B(指针)+ 8B(主键)= 16B
// - 非叶子节点可存储:16KB / 16B = 1024 个索引项
// - 树高度:log_1024(10,000,000) ≈ 3
// 3 次磁盘 I/O 就能找到任何数据2. 支持范围查询
-- B+树:顺序扫描叶子节点
SELECT * FROM users WHERE age BETWEEN 18 AND 25;
-- 只需要:
-- 1. 定位到 age=18 的叶子节点
-- 2. 沿着链表顺序访问到 age=253. 查询性能稳定
B 树:数据可能在任何层级
- 查询 1:根节点(1 次 I/O)
- 查询 2:叶子节点(3 次 I/O)
B+树:数据都在叶子节点
- 所有查询:3 次 I/O(性能稳定)4. 支持全表扫描
-- B+树:直接遍历叶子节点链表
SELECT * FROM users;
-- 无需回退到根节点,顺序访问即可InnoDB B+树实现细节
聚簇索引
-- 主键索引(聚簇索引)
CREATE TABLE users (
id INT PRIMARY KEY,
name VARCHAR(50),
age INT
);
-- B+树叶子节点存储:
-- | 主键 id | name | age | ... |非聚簇索引(辅助索引)
-- 辅助索引
CREATE INDEX idx_age ON users(age);
-- B+树叶子节点存储:
-- | age | 主键 id |
-- 查询过程(回表):
-- 1. 在 idx_age 中找到 age=18 的记录,得到 id
-- 2. 在聚簇索引中根据 id 找到完整数据扩展:B+树阶数计算
计算公式
假设:
- 磁盘页大小:16KB = 16384 Bytes
- 主键大小:8 Bytes (BIGINT)
- 指针大小:6 Bytes (InnoDB)
非叶子节点:
- 每个索引项:8 + 6 = 14 Bytes
- 可存储索引数:16384 / 14 ≈ 1170
叶子节点:
- 每行数据(假设):200 Bytes
- 可存储行数:16384 / 200 ≈ 82
树高度计算:
- 高度 1:1170 条记录
- 高度 2:1170 × 82 = 95,940 条记录
- 高度 3:1170 × 82 × 82 = 7,867,080 条记录
- 高度 4:1170 × 82 × 82 × 82 = 645,100,560 条记录实际案例
-- 假设表有 1000 万行数据
-- B+树高度:3
-- 查询性能:
-- 理论 I/O:3 次(根节点 → 中间节点 → 叶子节点)
-- 实际 I/O:1-2 次(根节点常驻内存)