MySQL索引一篇讲透:原理+分类+优化+面试题,面了12家都在问
MySQL索引是面试必考题也是日常优化关键。从B+树原理到联合索引最左前缀,从索引失效8大场景到EXPLAIN分析实战,一篇全讲透。面了十几家公司的经验总结,看完面试和工作都能用上。
前言:为什么MySQL索引是面试必考题?
最近跳槽季,前后面了十几家公司,从中小厂到互联网大厂,每一轮技术面都问到了MySQL索引。
不是问B+树的原理,就是问索引失效的场景,再不就是SQL优化的实战。问法千奇百怪,但核心考的就那么几个点。
今天一篇把这些全讲透。不管你是准备面试还是日常工作优化,都能用上。
一、索引的本质:为什么需要索引?
用一句话说清楚索引是什么:
索引就是书的目录。没有索引,查数据就是一页页翻全书(全表扫描)。有了索引,直接翻到对应页码就能找到数据。
MySQL 里索引是在存储引擎层实现的,不是 Server 层。所以不同存储引擎的索引实现方式不同。我们最常用的 InnoDB,用的是 B+ 树 索引。
相比二叉搜索树、红黑树、哈希表,B+ 树有几个核心优势:
- 树的高度低: 一个千万级表,B+ 树的高度一般是 3-4 层。意味着查一条数据最多只要 3-4 次磁盘 IO。
- 范围查询高效: B+ 树的叶子节点通过双向链表相连,可以直接范围遍历。
- 全节点存储数据: B+ 树的非叶子节点只存索引 key,不存数据,所以每个节点能存更多的 key,树更矮。
对比一下:如果用红黑树,高度大约是 25 层(2^25 ≈ 3300万),查一次要 25 次 IO,慢了一个数量级。
二、索引的分类
MySQL 索引可以从多个维度分类:
按数据结构分:
- B+ 树索引: InnoDB 的默认索引类型,支持范围查询和排序
- 哈希索引: Memory 引擎默认,等值查询极快,但不支持范围查询
- 全文索引: 适用于大文本的模糊搜索,MyISAM 和 InnoDB 都支持
按逻辑功能分:
- 主键索引: 每张表只能有一个,不能为 NULL,InnoDB 中就是聚簇索引
- 唯一索引: 索引列的值必须唯一,但允许有一个 NULL
- 普通索引: 没有任何限制,只是加速查询
- 全文索引: 用于大文本的搜索
- 组合索引: 多个列组合成一个索引,遵循最左前缀原则
按存储方式分(InnoDB):
- 聚簇索引: 数据和索引存储在一起。InnoDB 中主键索引就是聚簇索引。叶子节点直接存储整行数据。
- 二级索引(辅助索引): 叶子节点存储的是主键值。通过二级索引查数据需要回表(先查二级索引拿到主键,再通过主键索引查数据)。
三、联合索引的最左前缀原则
联合索引是面试最高频的考点之一,必须理解透彻。
假设我们建了一个联合索引 (a, b, c),那么:
能用到索引的查询:
- WHERE a = 1
- WHERE a = 1 AND b = 2
- WHERE a = 1 AND b = 2 AND c = 3
- WHERE a = 1 ORDER BY b — a 用索引,b 用索引排序
- WHERE b = 2 AND a = 1 — MySQL 优化器会调整顺序,能用索引
不能用到索引的查询:
- WHERE b = 2 — 没有 a,无法使用
- WHERE c = 3 — 没有 a 也没有 b
- WHERE a = 1 AND c = 3 — a 用到索引,但 c 用不到(跳过了 b)
简单记忆法: 就像爬楼梯,必须先上第一级,才能上第二级,不能跳过。从最左边开始,连续匹配才能用到索引。
四、索引失效的 8 种场景
下面这些场景是面试高频题,也是实际开发中容易踩的坑:
1. 对索引列使用函数
WHERE DATE(create_time) = '2026-05-08' -- 索引失效 WHERE create_time >= '2026-05-08' AND create_time < '2026-05-09' -- 索引生效
2. 隐式类型转换
-- user_id 是 varchar 类型 WHERE user_id = 12345 -- MySQL 会把 12345 转成字符串,能走索引 -- 但反过来,如果 user_id 是 int,传了字符串 '12345',也可能导致索引失效 -- 关键是看字段类型和传入类型是否一致
实际上这个要看具体情况。int 转 varchar 不走索引,varchar 转 int 可能走也可能不走,取决于数据。最好的做法是 类型保持一致。
3. 不满足最左前缀
上面已经讲过了,联合索引不满足最左前缀直接失效。
4. 使用 LIKE 模糊匹配以 % 开头
WHERE name LIKE '%张三%' -- 不走索引 WHERE name LIKE '张三%' -- 走索引(范围查询)
5. 使用 OR 连接条件
WHERE a = 1 OR b = 2 -- 如果 a 和 b 都有索引,会走索引合并 -- 但如果其中一个没有索引,或者 MySQL 认为走索引还不如全表扫描快,就会走全表扫描
6. 索引列参与计算
WHERE price * 1.1 > 100 -- 不走索引 WHERE price > 100 / 1.1 -- 走索引(把计算移到右边)
7. 使用 NOT IN 和 !=
NOT IN 和 != 通常不走索引,因为 MySQL 认为这类查询返回的数据量可能很大,走索引还不如全表扫描快。但如果是 NOT EXISTS,有时候可以走索引。
8. 数据分布不均匀
如果索引列的数据分布严重不均匀(比如性别列,90% 都是男),MySQL 优化器可能会认为走索引还不如全表扫描快,就放弃索引了。
五、如何分析 SQL 是否走索引?
用 EXPLAIN 关键字:
EXPLAIN SELECT * FROM user WHERE age = 25;
重点关注几个字段:
- type: 访问类型,从好到差依次是 system > const > eq_ref > ref > range > index > ALL。看到 ALL 说明全表扫描,需要优化。
- key: 实际用到的索引名称。如果是 NULL,说明没用到索引。
- rows: MySQL 估计需要扫描的行数。行数越少越好。
- Extra: 额外信息。Using filesort 需要优化(不走索引排序),Using temporary 也要注意(用了临时表)。
六、常见 SQL 优化实战
场景 1:分页查询越往后越慢
SELECT * FROM order LIMIT 100000, 20; -- 很慢
优化方案:
-- 方案一:记录上次查询的最大 ID SELECT * FROM order WHERE id > 100000 LIMIT 20; -- 方案二:子查询方式 SELECT * FROM order WHERE id >= (SELECT id FROM order LIMIT 100000, 1) LIMIT 20;
场景 2:ORDER BY 导致文件排序
SELECT * FROM user WHERE age > 18 ORDER BY create_time; -- 可能 filesort
优化:建立联合索引 (age, create_time),让排序走索引。
场景 3:COUNT(*) 很慢
在 InnoDB 中,COUNT(*) 需要遍历索引统计行数。如果表很大,几个优化方法:
- 用 COUNT(1) 代替 COUNT(*),性能差别不大
- 单独建一张计数表,每次插入删除时更新
- 用 EXPLAIN 估算行数(不精确但快)
场景 4:避免 SELECT *
SELECT * 会返回所有列,如果表有 text/blob 等大字段,会大幅增加 IO 开销。只查需要的列:
SELECT id, name, age FROM user WHERE ...; -- 比 SELECT * 快很多
七、高频面试题
Q1:为什么 MySQL 用 B+ 树而不是 B 树?
B+ 树的非叶子节点不存数据,只存索引 key,所以每个节点可以存更多 key。同样数据量的情况下,B+ 树比 B 树更矮,IO 次数更少。而且 B+ 树的叶子节点通过链表相连,范围查询更高效。
Q2:什么是回表?怎么避免回表?
通过二级索引查到主键值后,再通过主键索引去查完整行数据,这个过程叫回表。
避免回表的方式是使用 覆盖索引——让查询的列全部包含在索引中。比如索引是 (name, age),查询 SELECT name, age FROM user WHERE name = '张三',直接在二级索引就拿到了数据,不需要回表。
Q3:一张表可以没有主键吗?
可以,但不推荐。如果 InnoDB 表没有显式定义主键,InnoDB 会选择一个非空的唯一索引作为主键。如果也没有唯一索引,InnoDB 会隐式生成一个 6 字节的 row_id 作为主键。但没有主键的表性能会明显下降。
Q4:什么是索引下推(ICP)?
MySQL 5.6 引入的优化。在联合索引中,如果有 WHERE 条件无法通过索引直接过滤,MySQL 5.6 之前需要把数据回表后再过滤。索引下推允许在存储引擎层就进行过滤,减少回表次数。
举例:索引 (city, age),查询 WHERE city = '北京' AND age > 30。5.6 之前:查到 city 为北京的所有记录 → 回表 → 过滤 age。5.6 之后:查到 city 为北京的记录时,直接在索引里过滤 age > 30,再回表,减少了回表次数。
Q5:数据量很大的表怎么加索引?
大表加索引会锁表,导致业务中断。建议的做法:
- 在业务低峰期操作
- 使用 pt-online-schema-change 工具(Percona Toolkit),通过触发器在不锁表的情况下完成 DDL
- 或者用 gh-ost(GitHub 开源的在线 DDL 工具)
Q6:如何排查慢 SQL?
开启慢查询日志:
SET GLOBAL slow_query_log = ON; SET GLOBAL long_query_time = 1; -- 超过 1 秒的 SQL 记录到日志
然后分析慢查询日志,结合 EXPLAIN 分析,找到问题 SQL 进行优化。
写在最后
MySQL 索引的知识点看起来很多,但核心就三条:
1. 理解 B+ 树的结构和优势 —— 这是基础
2. 掌握联合索引的最左前缀原则 —— 这是面试高频
3. 学会用 EXPLAIN 分析 SQL —— 这是实战技能
这三条掌握了,MySQL 索引相关的问题基本都能应对。
如果你在面试中遇到过其他 MySQL 索引的刁钻问题,欢迎在评论区分享 👇
更多推荐



所有评论(0)