MySQL 索引到底是什么?普通人也能看懂的通俗讲解
一、先从一个生活场景说起
想象一下,你是一家图书馆的管理员,图书馆里有 100万本书 ,但没有任何分类,全部堆在一个大仓库里。
有一天,读者跑来问你:
"请帮我找一本叫《活着》的书。"
你怎么办?只能从第一本开始,一本一本翻,运气好第50本找到,运气不好翻到第99万本。
读者等得不耐烦了,骂你:
"找一本书要半天,这图书馆还开什么开!"
这时候,你做了一件事------做了一个"索引"
你花了一天时间,把所有书名按拼音首字母排序,写在一个本子上:
css
A → 第3排第5个书架
B → 第7排第2个书架
...
H → 第12排第8个书架 ←《活着》在这里!
...
Z → 第20排第1个书架
下次再有人找《活着》,你不用翻书了,直接查本子:
H → 第12排第8个书架 → 走过去 → 拿到书。
30秒搞定。
这个"本子",就是 MySQL 的索引
把上面的场景翻译成技术语言:
| 生活场景 | MySQL 术语 |
|---|---|
| 100万本书 | 数据表中的100万行数据 |
| 一本一本翻 | 全表扫描(Full Table Scan) |
| 那个排序的本子 | 索引(Index) |
| 按拼音首字母排序 | B+Tree 索引结构 |
| 查本子再找书 | 索引查找 + 回表 |
一句话总结:索引就是一个"排好序的快速查找目录",帮你不用全表扫描就能找到数据。
二、没有索引 vs 有索引,差距有多大?
假设有一张用户表 user,有 1000万条记录 ,你要找 id = 9999999 的那个人:
❌ 没有索引
erlang
MySQL 的执行过程:
第1行 → 不是
第2行 → 不是
第3行 → 不是
...
第9999999行 → 是!找到了!
扫描了 9999999行 ,耗时可能 几秒钟甚至十几秒。
✅ 有索引(id 为主键,自带索引)
css
MySQL 的执行过程:
查 B+Tree → 走3层就定位到了 → 直接找到那一行
只查了 3次 ,耗时 0.001秒不到。
1000万行数据,差距是 几万倍。
三、索引到底长什么样?(B+Tree 通俗版)
MySQL 最常用的索引结构叫 B+Tree,别被名字吓到,它其实就是一棵"多叉查找树"。
想象你在玩一个猜数字游戏:
我心里想了1~1000之间的一个数,你来猜。
笨办法(全表扫描) :
1?不是。2?不是。3?不是......
聪明办法(二分查找) :
500?大了。250?小了。375?大了。312?小了......
B+Tree 就是这个思路的升级版 ,只不过它不是"二分"(每次分2份),而是 "多分" (每次分几百份)。
css
[500 | 1000 | 1500]
/ | \
[100|300] [600|800] [1200|1400]
/ | \ / | \ / | \
1-99 101-299 ... ... 1401-1999
↑
叶子节点存的才是真实数据(或者数据的地址)
关键特点:
- 树的高度很低(1000万数据,通常只有 3~4层)
- 每一层都排好序,查找时不用遍历,直接跳
- 最底层的叶子节点之间有链表相连,方便范围查询
比喻:就像你查字典,不是从第一页翻,而是先翻到"H"开头的部分,再翻到"huo"的部分,几下就到了。
四、索引不是银弹------它也有代价
很多新手觉得"那我把所有字段都加上索引不就完了?"
不行。索引是有代价的:
代价1:占空间
那个"本子"本身也要地方放。一张表的索引,可能比数据本身还大。
代价2:写操作变慢
你每次新增/修改/删除 一本书,不光要放书,还要更新那个本子。
markdown
INSERT 一条数据:
- 要把数据写入表 ✅
- 要把索引也插入 B+Tree ✅ ← 多了这一步!
UPDATE 一个字段:
- 如果这个字段有索引,还要去修改索引树 ✅ ← 又多了一步!
DELETE 一条数据:
- 删除数据 ✅
- 删除索引中的记录 ✅ ← 还是多了一步!
比喻:图书馆每进一本新书,你都要在本子上插入一行,还得保持排序。书越多,你越忙。
代价3:索引太多,优化器会"懵"
MySQL 优化器要从多个索引中选一个最优的,索引太多反而让它选错。
五、索引的常见类型(用人话说)
| 索引类型 | 通俗比喻 | 适用场景 |
|---|---|---|
| 主键索引(PRIMARY) | 身份证号,每个人唯一,自动建索引 | 每个表都该有 |
| 普通索引(INDEX) | 书本的拼音目录 | 经常用来查询的字段,如 email |
| 唯一索引(UNIQUE) | 身份证号目录,不允许重复 | username、phone |
| 联合索引(Composite) | 先按"姓氏"排,姓氏相同再按"名字"排 | WHERE last_name='张' AND first_name='三' |
| 全文索引(FULLTEXT) | 书的内容关键词检索 | 文章内容搜索 |
六、联合索引------最容易踩坑的地方
这是面试和实战中最高频的考点,用生活例子讲清楚。
假设你建了一个联合索引:(姓, 名)
markdown
相当于电话簿:先按姓氏排序,同姓的再按名字排序。
张三 → 张三丰
→ 张三李四(不是,姓张名三李四?不对,应该是张姓,三李四是名)
→ 张伟
李四 → 李四光
→ 李四
✅ 能用上索引的查询
sql
sql
WHERE 姓 = '张' -- 用上了!先找姓张的
WHERE 姓 = '张' AND 名 = '三' -- 用上了!精确定位
❌ 用不上索引的查询
sql
sql
WHERE 名 = '三' 没用!只知道名,不知道姓,相当于让你在电话簿里找所有名字叫"三"的人,你得翻整本!
WHERE 名 = '三' AND 姓 = '张' -- 注意!顺序反了也用不上!MySQL只认从左到右
🎯 最重要的一句话:联合索引遵循"最左前缀原则",从左往右匹配,断了就废了。
比喻:你按"省-市-区"建了索引,查询"浙江省杭州市"能用上,但直接查"杭州市"就用不上------因为你不知道是哪个省的杭州。
七、什么时候该建索引?什么时候不该建?
✅ 该建索引的情况
- 字段经常出现在
WHERE后面 - 字段经常用于
JOIN关联 - 字段经常用于
ORDER BY排序 - 字段经常用于
GROUP BY分组
❌ 不该建索引的情况
- 数据量很少的表(几百行,全表扫描更快)
- 经常被更新的字段(写代价太大)
- 区分度很低的字段(如"性别":男/女,建了索引也没啥用,因为一半数据都符合)
"性别"这种字段,索引查找后还要回表扫描一半的数据,还不如直接全表扫描快。
八、怎么看你的索引有没有生效?
用 EXPLAIN 命令,这是 MySQL 给你的"体检报告":
sql
sql
EXPLAIN SELECT * FROM user WHERE name = '张三';
输出结果关注这几列:
| 列名 | 你该看什么 | 理想值 |
|---|---|---|
type |
访问类型 | ref 或 range 或 const(越左越好) |
key |
实际用了哪个索引 | 有值就好,NULL = 没用上索引 |
rows |
预估扫描行数 | 越少越好 |
Extra |
额外信息 | 出现 Using index 最好(覆盖索引,不用回表) |
最怕看到的:
ini
type = ALL → 全表扫描,完蛋
key = NULL → 没用索引,完蛋
rows = 1000000 → 扫描100万行,完蛋
九、一张图总结全文
vbnet
┌─────────────────────────────────────────┐
│ MySQL 索引 一张图看懂 │
├─────────────────────────────────────────┤
│ │
│ 是什么? → 排好序的查找目录(像字典) │
│ │
│ 为什么快? → B+Tree,3~4层找到数据 │
│ 全表扫描要几百万次 │
│ │
│ 代价? → 占空间 + 写变慢 │
│ │
│ 怎么建? → WHERE/JOIN/ORDER/GROUP的字段 │
│ 别建在性别这种低区分度字段上 │
│ │
│ 联合索引? → 最左前缀原则,断了就废 │
│ 像电话簿:先姓后名 │
│ │
│ 怎么检查? → EXPLAIN 看 type 和 key │
│ │
└─────────────────────────────────────────┘
十、最后一句话
索引的本质就是"空间换时间"------你多花点空间存一个目录,换来查询速度提升几千倍。但目录也不是越多越好,建对了是神器,建错了是负担。
就像图书馆那个本子------该建的建,不该建的别建,建了就要维护好。
这就是 MySQL 索引,没那么神秘。 📖