目录
一、索引到底是什么?
二、为什么 MySQL 偏偏选 B + Tree?
1. 二叉树:看起来快,实际坑最多
2. 红黑树:平衡了,但还是不够好
3. Hash 表:精确查询神器,范围查询废物
4. B-Tree(B 树):比红黑树好,但还有优化空间
5. B+Tree(B + 树):MySQL 的最终选择 ✅
6、B + Tree和其他表的区别
五大索引数据结构全面对比表
7、MySQL 索引选型决策表
MySQL 索引选型决策表
8、核心总结
三、两种存储引擎的索引实现(MyISAM vs InnoDB)
1. MyISAM:非聚集索引(索引和数据分离)
2. InnoDB:聚集索引(索引和数据在一起)✅ 重点
四、InnoDB 的灵魂拷问(新手必懂)
1. 为什么 InnoDB 表必须建主键?
2. 为什么推荐用整型自增主键,而不是 UUID?
3. 为什么非主键索引存的是主键值,而不是数据地址?
五、MySQL 的性能加速器 ——Buffer Pool
六、联合索引与最左前缀原理
七、新手索引避坑指南
总结
作为后端开发新手,你一定听过 “加索引能让查询变快”,但很多人只知其然不知其所以然:为什么加了索引反而更慢?为什么主键推荐用自增整型?联合索引到底怎么用?
本文用最通俗的类比 + 实战例子,带你从底层数据结构搞懂 MySQL 索引,看完就能直接用到项目中。
一、索引到底是什么?
索引的本质:帮助 MySQL 高效获取数据的排好序的数据结构。
用生活中的例子理解:
- 没有索引的表 = 一本没有目录的字典,想查 “数” 字,你得从第一页翻到最后一页(全表扫描)。
- 有索引的表 = 带拼音目录的字典,先在目录找到 “shu” 对应的页码,直接翻到那一页(快速定位)。
反例:一张 100 万条数据的用户表,没有索引时执行SELECT * FROM user WHERE id=100000,MySQL 需要遍历 100 万条记录,耗时可能超过 1 秒;加了索引后,查询耗时能降到 1 毫秒以内。
二、为什么 MySQL 偏偏选 B + Tree?
MySQL 支持多种索引数据结构,但最终默认使用B + 树,我们一个个看其他结构为什么不行。
1. 二叉树:看起来快,实际坑最多
- 原理:每个节点最多 2 个子节点,左子节点 < 父节点 < 右子节点。
- 致命问题:极端情况退化成链表!例子:按顺序插入 1、2、3、4、5,二叉树会变成一条直线,查 5 需要遍历 5 个节点,和全表扫描没区别。
2. 红黑树:平衡了,但还是不够好
- 原理:自平衡二叉树,通过旋转保证树的高度不会太高。
- 问题:树的高度还是太高!100 万条数据,红黑树的高度约 20 层。每次查询需要读 20 次磁盘(每次 IO 只能读一层节点),而磁盘 IO 是数据库性能的最大杀手。
3. Hash 表:精确查询神器,范围查询废物
- 原理:对索引的 key 做一次 Hash 计算,直接得到数据的存储地址。
- ✅ 优点:等值查询(=、IN)极快,一次计算就能定位数据。例子:
SELECT * FROM user WHERE name='Alice';一次 Hash 运算就能找到 Alice 的存储位置。 - ❌ 缺点:
- 完全不支持范围查询:
SELECT * FROM user WHERE age>20;Hash 无法处理,只能全表扫描。 - 存在 Hash 冲突:两个不同的 key 算出相同的 Hash 值,需要用链表解决,影响性能。
- 完全不支持范围查询:
4. B-Tree(B 树):比红黑树好,但还有优化空间
- 原理:多路平衡查找树,每个节点可以存多个索引和数据,所有叶子节点在同一层,索引从左到右递增排列,是专门为磁盘存储设计的数据结构。
- 优点:树的高度大大降低,100 万条数据只需要 3-4 层。
- 问题:每个节点都存储完整数据,导致每个节点能存的索引数量有限,树的高度还是不够低。
5. B+Tree(B + 树):MySQL 的最终选择 ✅
B + 树是B-Tree 的优化变种,也是 MySQL InnoDB 存储引擎唯一默认使用的索引结构。它完美解决了 B-Tree、红黑树、Hash 表的所有缺点,是专门为数据库磁盘存储和查询场景量身打造的数据结构:
- 非叶子节点只存索引,不存数据:每个节点能存储更多索引,树的高度进一步降低(100 万条数据只要 3 层)。
- 叶子节点包含所有索引字段和完整数据:所有查询最终都会走到叶子节点,保证查询性能稳定。
- 叶子节点之间用双向指针连接:极大提升区间查询性能。例子:
SELECT * FROM user WHERE id BETWEEN 18 AND 30;只需要找到 id=18 的叶子节点,然后顺着指针向后遍历,就能一次性拿到 18-30 的所有数据,不用再从根节点重新查找。
6、B + Tree和其他表的区别
| 对比维度 | 二叉树 | 红黑树 | Hash 表 | B-Tree | B + 树(MySQL 默认) |
|---|---|---|---|---|---|
| 本质 | 二叉搜索树 | 自平衡二叉树 | 哈希表 + 拉链法 | 平衡多路查找树 | B-Tree 优化版,叶子节点链表 |
| 时间复杂度 | O (n)(最坏) | O(log₂n) | O (1)(最好)O (n)(冲突) | O (logₘn)(m 为阶数) | O(logₘn) |
| 树高(100 万条数据) | ~100 万层(最坏) | ~20 层 | 无树高概念 | ~3 层 | ~3 层(比 B-Tree 更低) |
| 节点存储内容 | 1 个索引 + 数据 | 1 个索引 + 数据 | 哈希值 + 数据地址 | 多个索引 + 数据 | 非叶子:仅索引叶子:所有索引 + 数据 |
| 每个节点存索引数 | 1 | 1 | 1 | 几百个 | 上千个(16K 页≈1000 个) |
| 磁盘 IO 次数(100 万条) | ~100 万次 | ~20 次 | ~1 次 | ~3 次 | ~3 次(更少) |
| 等值查询性能 | 差 | 一般 | 极快 | 快 | 快 |
| 范围查询性能 | 极差 | 差 | 完全不支持 | 一般 | 极好 |
| 排序(ORDER BY) | 极差 | 差 | 完全不支持 | 一般 | 极好(本身有序) |
| 分组(GROUP BY) | 极差 | 差 | 完全不支持 | 一般 | 极好 |
| 联合索引支持 | 不支持 | 不支持 | 不支持(无最左前缀) | 支持 | 完美支持最左前缀 |
| 覆盖索引支持 | 不支持 | 不支持 | 不支持 | 支持 | 完美支持 |
| 查询性能稳定性 | 极差 | 一般 | 不稳定(冲突影响) | 不稳定(根节点可能命中) | 极稳定(都到叶子节点) |
| 存在的问题 | 极端情况退化成链表 | 树高过高,IO 多 | 哈希冲突,不支持范围 | 非叶子节点存数据,树高不够低 | 无致命缺点 |
| MySQL 支持情况 | 不支持 | 不支持 | Memory 引擎支持InnoDB 自适应 Hash | 不支持 | 所有引擎默认支持 |
| 适用场景 | 无 | 内存数据结构(Java 集合) | 纯等值查询(用户登录) | 文件系统索引 | 所有数据库通用场景 |
7、MySQL 索引选型决策表
看完上面的对比,新手可以直接按照这个表选择索引类型:
| 业务场景 | 推荐索引类型 | 不推荐索引类型 |
|---|---|---|
| 通用查询(等值 + 范围 + 排序 + 分组) | B + 树索引 | 所有其他类型 |
| 纯等值查询,且 QPS 极高(如用户登录) | Memory 引擎 + Hash 索引 | B + 树索引(性能稍差) |
| 联合查询(多个字段组合查询) | B + 树联合索引(遵循最左前缀) | Hash 索引 |
| 只需要查询索引包含的字段 | B + 树覆盖索引 | 普通索引(需要返表) |
| 全文搜索(关键词模糊查询) | 全文索引(FULLTEXT)或 Elasticsearch | B + 树索引(% xxx% 失效) |
8、核心总结
- B + 树是数据库索引的终极形态:它结合了 B-Tree 的低树高优势和 Hash 表的有序链表优势,完美适配数据库的所有查询场景。
- MySQL 选择 B + 树的根本原因:树高低(磁盘 IO 少)、范围查询性能好、排序分组支持好、性能稳定。
- InnoDB 聚集索引是核心:主键索引叶子节点存完整数据,二级索引存主键值,这是 InnoDB 所有优化的基础。
- 99% 的场景下,B + 树索引都是最佳选择:只有当你确定业务只有纯等值查询且对性能要求极高时,才考虑 Hash 索引。
三、两种存储引擎的索引实现(MyISAM vs InnoDB)
MySQL 最常用的两个存储引擎,索引实现天差地别,现在几乎所有项目都用 InnoDB,但了解 MyISAM 能帮你更好理解聚集索引。
1. MyISAM:非聚集索引(索引和数据分离)
- 文件结构(每张表对应 3 个文件):
.frm:元数据文件,存储表结构(字段名、字段类型等).MYD:数据文件,存储真实的表数据.MYI:索引文件,存储所有索引
- 查询过程(需要两次磁盘 IO):例子:
SELECT * FROM user WHERE id=15;- 去
.MYI索引文件中找到 id=15 对应的数据地址 - 根据地址去
.MYD数据文件中读取完整数据
- 去
- 特点:查询速度比 InnoDB 慢,不支持事务和外键,现在基本被淘汰。
2. InnoDB:聚集索引(索引和数据在一起)✅ 重点
文件结构(每张表对应 2 个文件):
.frm:元数据文件(表结构).idb:数据 + 索引文件,整个表的数据和所有索引都存在这一个文件里
核心概念:聚集索引(主键索引)InnoDB 表的数据文件本身就是按 B + 树组织的,主键索引的叶子节点直接存储完整的数据记录。例子:
SELECT * FROM user WHERE id=15;直接在主键 B + 树中找到 id=15 的叶子节点,就能拿到所有数据,不需要返表,速度极快。非聚集索引(二级索引)非主键索引(比如 name、age 索引)的叶子节点不存储完整数据,只存储主键值。例子:
SELECT * FROM user WHERE name='Alice';- 先在 name 索引的 B + 树中找到 'Alice' 对应的主键值 18
- 再拿着主键 18 去主键索引的 B + 树中,找到完整的数据记录这个过程叫做返表查询,需要两次磁盘 IO,比主键查询慢。
四、InnoDB 的灵魂拷问(新手必懂)
1. 为什么 InnoDB 表必须建主键?
如果不显式指定主键,MySQL 会自动选择一个唯一非空列作为主键;如果连这样的列都没有,MySQL 会隐式生成一个 6 字节的rowid作为主键。
隐式主键不仅增加数据库的额外开销,还会让你无法利用聚集索引的优势,所以一定要显式为每张 InnoDB 表创建主键。
2. 为什么推荐用整型自增主键,而不是 UUID?
- 整型占用空间更小:int 占 4 字节,bigint 占 8 字节,而 UUID 是 32 位随机字符串,占 36 字节。索引越小,B + 树每个节点能存的索引越多,树越矮,查询越快。
- 自增主键有序:插入数据时,永远在 B + 树的最后一个叶子节点追加,不会导致节点分裂和数据移动,插入性能极高。
- UUID 的致命坑:UUID 是随机字符串,插入时会随机插到 B + 树的中间位置,导致频繁的节点分裂和大量数据移动,插入性能暴跌。对比示例:✅ 自增主键插入:1→2→3→4→5,都在末尾追加,不用动其他数据。❌ UUID 插入:a1→b2→c3→a0,a0 要插到 a1 前面,节点满了就要分裂,性能差 10 倍以上。
3. 为什么非主键索引存的是主键值,而不是数据地址?
- 保证数据一致性:当数据更新或移动时,只需要更新主键索引的叶子节点,所有二级索引都不用修改。如果存数据地址,数据移动时所有二级索引都要更新,容易出现不一致。
- 节省存储空间:避免在每个二级索引中都存储完整的数据记录,大大节省磁盘空间。
五、MySQL 的性能加速器 ——Buffer Pool
很多人不知道,MySQL 查询数据不是直接读磁盘,而是先读内存中的Buffer Pool(缓冲池)。
- 默认大小:16M(生产环境建议设为物理内存的 50%-70%)
- 作用:缓存热点数据和索引,减少磁盘 IO 次数。
- 实战例子:第一次执行
SELECT * FROM user WHERE id=15;:从磁盘读入 Buffer Pool,耗时约 10ms。第二次执行同样的 SQL:直接从 Buffer Pool 读取,耗时约 0.1ms,快了 100 倍。
六、联合索引与最左前缀原理
联合索引是由多个字段组成的索引,比如idx_name_age_city(name, age, city),它遵循最左前缀原则:查询时必须从索引的最左边字段开始匹配,否则用不到索引。
✅ 能用到索引的查询:
-- 匹配最左字段name SELECT * FROM user WHERE name='Alice'; -- 匹配前两个字段name+age SELECT * FROM user WHERE name='Alice' AND age=20; -- 匹配全部三个字段 SELECT * FROM user WHERE name='Alice' AND age=20 AND city='Beijing'; -- MySQL优化器会自动调整字段顺序,也能用到索引 SELECT * FROM user WHERE age=20 AND name='Alice';❌ 不能用到索引的查询:
-- 跳过最左字段name SELECT * FROM user WHERE age=20; SELECT * FROM user WHERE city='Beijing'; SELECT * FROM user WHERE age=20 AND city='Beijing';最佳实践:创建联合索引时,把最常用的查询字段放在最左边,区分度高的字段放在前面。
七、新手索引避坑指南
不是所有字段都要建索引:
- ✅ 适合建索引:经常出现在
WHERE、ORDER BY、GROUP BY中的字段。 - ❌ 不适合建索引:
- 区分度低的字段:比如性别(只有男 / 女),建了索引也没什么用。
- 经常更新的字段:更新时不仅要改数据,还要更新索引,开销很大。
- 数据量小的表:只有几百条数据,全表扫描比索引查询更快。
- ✅ 适合建索引:经常出现在
避免索引失效:在索引字段上使用函数、运算、隐式类型转换,都会导致索引失效。反例:
SELECT * FROM user WHERE YEAR(create_time)=2026;(create_time 上的索引完全用不到)
总结
- 索引的本质是排好序的数据结构,相当于数据库的 “目录”。
- MySQL 默认用B + 树做索引,因为它树高低、支持范围查询、性能稳定。
- InnoDB 用聚集索引,主键索引叶子节点存完整数据,二级索引存主键值,会有返表查询。
- 一定要用整型自增主键,绝对不要用 UUID 做主键。
- 联合索引遵循最左前缀原则,查询必须从最左边的字段开始匹配。