MySQL 索引底层原理:B+ 树深度剖析
索引是 MySQL 性能优化的核心,理解 B+ 树原理是掌握索引优化的前提。
一、为什么需要索引?
没有索引时,查询一条记录需要全表扫描,时间复杂度 O(n)。当表数据量达到百万级,查询会非常慢。
索引的本质:排好序的快速查找数据结构。
二、数据结构的演进
1. 二叉搜索树(BST)
左子树 < 根 < 右子树,查找效率 O(log n)。
问题:当数据本身有序时,BST 会退化为链表,查找效率 O(n)。
2. 平衡二叉树(AVL)
通过旋转保持平衡,左右子树高度差不超过 1。
问题:每个节点只存一个元素,树高仍然较高。百万数据需要 20 层,磁盘 IO 次数多。
3. B 树(多路搜索树)
每个节点可以存多个元素,且有多个子节点。
[13 | 26 | 38]
/ | | \
[1,3,7] [15,18] [28,32] [40,45,48]
优势:树高降低,IO 次数减少。
缺点:每个节点都存数据,单个节点能放的元素有限;范围查询需要中序遍历。
4. B+ 树(MySQL 的选择)
在 B 树基础上的改进:
- 非叶子节点只存索引,不存数据 → 单个节点能存更多索引 → 树更矮
- 所有数据都在叶子节点 → 查询性能稳定
- 叶子节点之间用双向链表连接 → 范围查询高效
[13 | 26 | 38] ← 非叶子节点
/ | | \
[1,3,7] [13,15,18] [26,28,32] [38,40,45] ← 叶子节点
⇄ ⇄ ⇄ ⇄
三、MySQL 索引存储大小估算
InnoDB 默认页大小 16KB。
假设主键 bigint 占 8 字节,指针 6 字节:
- 非叶子节点:16KB / (8+6) ≈ 1170 个指针
- 叶子节点:假设一行数据 1KB → 16 行
三层 B+ 树可存储:
$$1170 \times 1170 \times 16 \approx 2190万 \text{ 行数据}$$
也就是说,千万级表查询只需要 3 次磁盘 IO!
四、索引的分类
按数据结构分
- B+ 树索引:默认
- Hash 索引:Memory 引擎,等值查询快
- 全文索引:FULLTEXT,文本搜索
按物理存储分
- 聚簇索引:数据和索引在一起(主键索引)
- 非聚簇索引:数据和索引分开(二级索引)
按逻辑功能分
- 主键索引:唯一非空
- 唯一索引:唯一
- 普通索引:无约束
- 联合索引:多列组合
五、回表与索引覆盖
回表
-- 假设 name 上有普通索引
SELECT * FROM user WHERE name = '小宇宙';
执行过程:
- 在 name 索引树上找到「小宇宙」对应的主键 ID
- 再到主键索引树上根据 ID 查找完整数据
两次索引查找,称为回表。
索引覆盖
-- 只查询索引列,无需回表
SELECT id, name FROM user WHERE name = '小宇宙';
索引中包含了所有查询字段,无需回表,性能大幅提升。
六、最左前缀原则
联合索引 (a, b, c) 的排序规则是:先按 a 排序,a 相同按 b,b 相同按 c。
-- ✅ 命中索引
WHERE a = 1
WHERE a = 1 AND b = 2
WHERE a = 1 AND b = 2 AND c = 3
-- ❌ 无法命中索引
WHERE b = 2
WHERE c = 3
WHERE b = 2 AND c = 3
七、索引失效场景
-- 1. 计算操作
WHERE age + 1 = 18
-- 2. 函数操作
WHERE DATE(create_time) = '2026-06-24'
-- 3. 类型转换
WHERE phone = 13800000000 -- phone 是 varchar
-- 4. LIKE 左模糊
WHERE name LIKE '%小宇宙'
-- 5. OR 连接非索引列
WHERE a = 1 OR b = 2 -- b 无索引
-- 6. 不符合最左前缀
八、EXPLAIN 实战
EXPLAIN SELECT * FROM user WHERE name = '小宇宙';
重点关注:
| 字段 | 含义 |
| ---- | ---- |
| type | 访问类型,最好 ref/const,最差 ALL |
| key | 实际使用的索引 |
| rows | 估算扫描行数 |
| Extra | Using index(覆盖索引)/ Using filesort(额外排序) |
九、小结
- B+ 树是 MySQL 索引的首选,矮胖 + 链表适合磁盘存储
- 三层 B+ 树能存储千万级数据
- 联合索引遵循最左前缀原则
- 尽量做到索引覆盖,避免回表
小宇宙总结:理解索引的本质,是写 SQL 调优的根。慢查询 90% 都是索引没建好。