数据库面试题里,MySQL 索引调优是永远绕不开的高频板块。不管是校招还是社招,只要简历里写了 MySQL,面试官大概率会从“B+树”一路追到“分页优化”:聚簇索引、回表、最左前缀、索引失效、EXPLAIN、MRR、索引下推……网上很多面经内容零散,背完上一题忘下一题,核心原因是没有形成体系。
这篇内容把数据库 mysql 索引调优常见的面试题完整盘点成 50 问,分成索引基础、索引失效、EXPLAIN 分析、优化实战、进阶原理五个维度。不只是列题目,每个重点问题都给了可直接用于面试的简答思路和加分点。第一篇先说清楚:MySQL 为什么选 B+ 树、什么是回表、哪些写法会让索引失效、EXPLAIN 到底怎么看、分页排序怎么优化、线上慢 SQL 怎么排查。准备 Java 后端、测试、运维方向面试的同学,或者想系统补 MySQL 索引知识的开发者,建议直接收藏备用。
1. 索引调优 50 问速览:先建立完整知识地图
面试中的“夺命连环问”,表面是考知识点,实际是考知识结构。我先用 5 张表把 50 问的覆盖范围列出来,后续章节再按重点逐个展开。
1.1 索引基础篇(第 1-10 问)
| 编号 | 面试题 | 高频度 |
|---|---|---|
| 1 | InnoDB 为什么选择 B+ 树作为索引结构? | 极高 |
| 2 | 为什么不用红黑树、哈希表或 B 树? | 高 |
| 3 | 聚簇索引和非聚簇索引有什么区别? | 极高 |
| 4 | InnoDB 二级索引的叶子节点存的是什么? | 极高 |
| 5 | 什么是回表?如何避免回表? | 极高 |
| 6 | 覆盖索引为什么快? | 极高 |
| 7 | 为什么建议使用自增主键?UUID 主键有什么影响? | 高 |
| 8 | 普通索引和唯一索引在查询和更新上有什么区别? | 中 |
| 9 | 哪些列适合建索引?哪些列不适合? | 高 |
| 10 | 联合索引的字段顺序怎么定? | 极高 |
1.2 索引失效场景篇(第 11-20 问)
| 编号 | 面试题 | 高频度 |
|---|---|---|
| 11 | 什么是最左前缀原则? | 极高 |
| 12 | 在索引列上使用函数为什么会导致失效? | 高 |
| 13 | 隐式类型转换为什么会导致失效? | 高 |
| 14 | 字符串字段和字符集不一致会导致失效吗? | 中 |
| 15 | OR 查询一定不走索引吗? | 高 |
| 16 | LIKE 模糊查询什么时候能走索引? | 极高 |
| 17 | NOT IN、!=、IS NULL 会走索引吗? | 高 |
| 18 | 哪些常见写法让联合索引只命中了一部分? | 极高 |
| 19 | 为什么优化器不一定选择索引?直方图有什么用? | 中 |
| 20 | 索引下推是什么?怎么证明它生效了? | 高 |
1.3 EXPLAIN 与慢查询篇(第 21-30 问)
| 编号 | 面试题 | 高频度 |
|---|---|---|
| 21 | EXPLAIN 主要看哪些列? | 极高 |
| 22 | type 字段从好到差怎么排? | 极高 |
| 23 | key_len 怎么计算? | 高 |
| 24 | Extra 出现 Using filesort 说明什么? | 高 |
| 25 | Using temporary 说明什么? | 中 |
| 26 | Using index 和 Using index condition 有什么区别? | 高 |
| 27 | 怎么判断一条 SQL 是否发生了回表? | 高 |
| 28 | rows 扫描行数怎么理解? | 中 |
| 29 | 慢查询日志怎么开启和解读? | 高 |
| 30 | 批量写入时索引对性能有哪些影响? | 中 |
1.4 优化实战篇(第 31-40 问)
| 编号 | 面试题 | 高频度 |
|---|---|---|
| 31 | LIMIT 分页 offset 很大为什么慢?怎么优化? | 极高 |
| 32 | ORDER BY 排序怎么走索引? | 极高 |
| 33 | GROUP BY 分组怎么优化? | 高 |
| 34 | COUNT(*) 为什么慢?怎么优化? | 高 |
| 35 | JOIN 查询怎么优化?驱动表怎么选? | 极高 |
| 36 | 大表加索引要注意什么? | 高 |
| 37 | 为什么索引不是越多越好? | 高 |
| 38 | 冗余索引和重复索引怎么识别清理? | 中 |
| 39 | 如何查看表上每个索引的使用情况? | 中 |
| 40 | 优化器选错索引怎么办? | 中 |
1.5 进阶原理篇(第 41-50 问)
| 编号 | 面试题 | 高频度 |
|---|---|---|
| 41 | 什么是 MRR?它解决了什么问题? | 中 |
| 42 | 什么是索引合并 index merge? | 中 |
| 43 | change buffer 对二级索引有什么影响? | 中 |
| 44 | 普通索引和唯一索引在 change buffer 上有什么不同? | 中 |
| 45 | 自适应哈希索引是什么?需要人工建吗? | 低 |
| 46 | MVCC 下走索引查询读到的是快照还是最新数据? | 高 |
| 47 | 当前读加锁时,锁是加在索引记录上的吗? | 高 |
| 48 | 索引基数统计不准导致选错索引怎么办? | 中 |
| 49 | 线上大表加索引用 Online DDL 还是 gh-ost? | 中 |
| 50 | 一条 SQL 已经有索引但还是慢,接下来怎么排查? | 极高 |
这 50 问不是要你全部背诵。面试中 80% 的追问会落在第 5、6、10、11、16、21、22、27、31、32、35、50 这几题上。下面逐个拆解。
2. 索引基础连环问:从 B+ 树到回表
这一组问题属于“一开口就知道你有没有背过面试题”的送命题,回答时不要只背结论,要能画出索引结构的关键差异。
2.1 InnoDB 为什么选用 B+ 树
核心答案:B+ 树矮胖,磁盘 IO 次数少;叶子节点有序存储,天然支持范围查询;非叶子节点只存键值和指针,扇出高,一层能覆盖更多数据。
加分点:
- 红黑树是二叉树,树高约 log2N,数据量千万级时,树高超 20 层,查询一次可能要 20 次磁盘 IO,不可接受。
- 哈希索引等值查询极快,但不支持范围查询和排序。
- B 树非叶子节点存数据,页内能存下的键值数量变少,相同数据量树会更高,范围查询也比 B+ 树复杂。
- B+ 树的叶子节点用双向链表连接,
SELECT * FROM t WHERE id > 10这类范围扫描可以顺序遍历。
回答时最好提一句“InnoDB 的数据页默认 16KB,层数越少,一次查询读取的页越少”,这句话比单纯背“B+树更矮”更有含金量。
2.2 聚簇索引、非聚簇索引和回表
InnoDB 的主键索引是聚簇索引,叶子节点保存整行数据;二级索引是非聚簇索引,叶子保存索引列值和主键值。
回表:走二级索引查出主键值后,再回聚簇索引查整行,这个过程叫回表。避免回表最常见的手段是覆盖索引:
-- 假设 idx_user_age(age) 已存在 -- 这个查询只需要 age 和主键 id,索引里都有,不需要回表 SELECT id, age FROM user WHERE age BETWEEN 20 AND 30; -- 这个查询要查 name,索引里没有,需要回表 SELECT id, age, name FROM user WHERE age BETWEEN 20 AND 30;面试追问一般会停在“覆盖索引是不是加越多越好”。这里要说明:覆盖索引本质是用空间换时间,写入时每个索引都要维护,索引列越多、越宽,写入成本越高。所以一般只给高频查询做覆盖索引,而不是所有查询都去建一个“大宽索引”。
2.3 自增主键还是 UUID
经常有面试题会问:主键用自增还是 UUID?
参考答案:InnoDB 聚簇索引按主键顺序组织。自增主键写入时是顺序追加,页分裂概率低;UUID 主键是无序的,插入时可能触发大量页分裂和页重组,随机 IO 变多,写入性能明显下降。另外 UUID 是 16 字节,自增 INT 是 4 字节、BIGINT 是 8 字节,二级索引叶子节点存主键值,主键越短,二级索引占用的空间也越小。
但要注意,如果业务层需要防止订单号泄露、需要全局唯一 ID,可以使用雪花算法生成的“趋势递增 ID”,而不是完全随机的 UUID。
2.4 联合索引的字段顺序
这是索引调优面试里的重点,回答要落到“索引最左前缀”和“区分度”两个维度。
原则:where 等值条件优先放前面;把区分度高的列放在联合索引左侧,可以减少扫描范围;把排序或分组字段纳入联合索引,可以避免 filesort;如果有范围条件,范围列通常放在最后。
示例:
-- 核心查询是: SELECT * FROM order_tab WHERE user_id = 12345 AND status = 1 ORDER BY create_time DESC; -- 建议联合索引:idx_user_status_time(user_id, status, create_time)为什么这样建?user_id 是等值,status 也是等值,create_time 用于排序。如果只建(user_id, create_time),status 过滤就只能回表后再做,而且 create_time 顺序在联合索引里会乱,可能仍然 filesort。
3. 索引失效的 8 种写法,面试官一问一个准
面试题里出现频率最高的是“什么情况下索引会失效”,实际线上排查也最常用。下面这几种情况基本覆盖面试官的追问范围。
3.1 最左前缀原则
联合索引(a, b, c),查询条件如果只写b或只写c,那这个联合索引就用不上。面试官喜欢追一句:如果只查a和c,部分能否走索引?
答案是可以。a会用于索引范围定位,c只能作为回表后的过滤条件,或者配合索引下推优化。这里不要回答成“走了索引就是全用上了”,要分清楚“有多少列真正参与索引扫描”。
3.2 在索引列上使用函数或表达式
-- 失效 SELECT * FROM user WHERE DATE(create_time) = '2026-01-01'; -- 可优化 SELECT * FROM user WHERE create_time >= '2026-01-01' AND create_time < '2026-01-02';MySQL 8.0 虽然支持函数索引,但默认情况下对索引列套函数,优化器无法直接使用原索引。更稳妥的思路是改写成范围条件,或者用函数索引。
3.3 隐式类型转换
-- phone 字段是 varchar,但参数传了数字 SELECT * FROM user WHERE phone = 13800000000;MySQL 会把字符串字段转换为数字再比较,索引失效。同理,字符集不一致也可能导致索引列无法直接比较,关联查询时两个表的 join 字段字符集不同,也可能影响索引命中。线上设计表结构时,关联字段尽量统一字符集。
3.4 OR、LIKE、NOT IN、!=
简单记法:OR 两边都是索引列时,优化器可能使用 index merge 或两个索引分别扫描后合并;只要有一边没索引,就会退化全表扫描。
LIKE 的规则是前缀匹配能走索引,LIKE '%abc'不走,LIKE 'abc%'走。如果业务确实需要右侧模糊,一个可行方案是把数据冗余成“倒排字段”或使用全文索引。
NOT IN、!=、IS NOT NULL不一定绝对失效。优化器会基于成本选择。比如IS NULL在 MySQL 里有时会走 ref,关键还要看索引分布和基数。
3.5 索引下推 ICP
索引下推可以理解为:二级索引遍历时,先对索引列做过滤,再回表,减少回表次数。
-- 联合索引 (age, name) SELECT * FROM user WHERE age > 20 AND name LIKE '张%';没有 ICP 时,先按 age 范围取主键,再回表后用 name 过滤;有 ICP 时,name 的过滤直接在二级索引内完成。EXPLAIN 的 Extra 列会出现Using index condition。这是 MySQL 5.6 之后的优化,面试时回答“减少了回表次数和 IO”就能拿分。
4. EXPLAIN 怎么看:从 type 到 Extra
面试官出这道题时,通常会给一条 SQL 让你读 EXECLAIN。重点不是背字段,而是能在 30 秒内判断“这条 SQL 到底哪里慢”。
4.1 必看的 6 个字段
type、key、key_len、rows、filtered、Extra是核心。
type从好到差通常排为:system > const > eq_ref > ref > range > index > ALL。
- const:主键或唯一索引等值查询。
- ref:普通索引等值查询。
- range:索引范围扫描。
- index:整棵二级索引树扫描,虽然不算全表,但也不便宜。
- ALL:全表扫描。
面试时能说出“ALL 是什么时候出现、index 和 ALL 有什么区别、range 是不是一定好用”就够了。
4.2 怎么判断有没有回表
看 Extra。
Using index:直接在索引树取到所有请求列,不需要回表。Using index condition:用到了索引下推,但可能还需要回表。- 如果 Extra 什么都没有,且 type 是 ref 或 range,大概率需要回表再取数据。
key_len可以判断联合索引实际用了多少列。比如(a, b, c)三个 int 列,key_len = 4说明只用到a,key_len = 8说明用到a,b。这个知识点在面试里很加分。
4.3 慢查询日志怎么开
配置方法:
[mysqld] slow_query_log = ON slow_query_log_file = /var/log/mysql/slow.log long_query_time = 1 log_queries_not_using_indexes = ON不用记太细,能在面试中说出“先开慢日志,抓出真正的慢 SQL,再 explain 分析”这个流程即可。
5. 索引调优实战:分页、排序、分组、join、count
这一部分是面试官判断你“是否真的做过优化”的关键。能答出前面基础题,可能只是背了面经;能讲清这里的场景,才有项目经验。
5.1 深分页优化
-- 深分页慢的写法 SELECT * FROM order_tab ORDER BY id LIMIT 100000, 20; -- 基于主键游标的写法 SELECT * FROM order_tab WHERE id > 100000 ORDER BY id LIMIT 20;第二种写法适合按主键或唯一排序字段分页的场景,本质上把“扫描 100020 行再扔掉 100000 行”变成“直接从 100001 行开始取 20 行”。
如果 where 条件不能直接用主键,可以用延迟关联:
SELECT o.* FROM order_tab o INNER JOIN ( SELECT id FROM order_tab ORDER BY id LIMIT 100000, 20 ) t ON o.id = t.id;先只查主键再 join 回原表,减少回表次数。
5.2 ORDER BY 和 GROUP BY
order by 要避免 filesort,核心思路是让排序字段和联合索引顺序一致,并且与 where 中已确定的等值列在同一条索引路径上。示例见 2.4。
group by 本质是一边分组一边排序。如果分组字段包含在联合索引内,可以避免临时表。如果业务不需要排序,可以加ORDER BY NULL让 MySQL 跳过排序,但 MySQL 8.0 下已不再推荐依赖这个写法,重点还是让 group 字段命中索引。
5.3 JOIN 优化
先说结论:小表驱动大表,join 字段必须走索引,关联字段类型要一致。
MySQL 执行 join 时常用 Nested Loop Join,外层表每取一行,都要去内层表索引里找匹配行。所以内层表的 join 字段有没有索引,直接决定性能。
回答模板:
- 用 EXPLAIN 看驱动表是否能用小表。
- 给 join 条件列建立合适索引。
- 避免把 join 的结果集做得特别大,能用子查询或临时表先缩小数据再关联。
- 如果业务对实时性要求不高,可以考虑在应用层拆分查询。
5.4 COUNT(*) 怎么优化
面试题常见的坑:InnoDB 里COUNT(*)不是 O(1) 操作,因为 InnoDB 要支持 MVCC,每行数据必须在事务可见性范围内统计,所以不能像 MyISAM 那样直接读元数据。优化手段:
- 对实时性要求不高的场景,用 information_schema.tables 的 rows 字段做近似值。
- 用缓存或汇总表。
- 让统计查询走辅助索引,因为辅助索引一般比主键索引小,扫描成本更低。
6. 进阶连环追问:MVCC、锁、索引统计与在线 DDL
能顶到这一层,基本是高级岗位或大厂面试了。这里的问题不需要背全,但至少要能自洽。
6.1 MVCC 和锁与索引的关系
InnoDB 的普通查询是快照读,走 undo log 版本链判断可见性;SELECT ... FOR UPDATE、UPDATE、DELETE是当前读,必须加载最新版本,锁就加在索引记录上。
所以面试官问“如果某条 SQL 走全表扫描,加锁范围是什么”,答案是“锁会作用在扫描到的所有索引记录上”,也就是锁范围很大。这也是为什么生产环境非常忌讳不带 where 条件的大更新:表面只 update 一行,实际可能锁全表。
6.2 选错索引怎么办
优化器选错索引,往往是因为索引基数统计不准,或者样本估算有偏差。处理手段:
ANALYZE TABLE t;重新统计索引信息。- 改写 SQL,引导优化器走另一个索引。
- 使用
FORCE INDEX强制指定索引,但要谨慎,业务数据量变化后强制索引可能反而更慢。 - MySQL 8.0 的直方图可以让优化器对字段值分布有更准确的判断,减少选错概率。
6.3 大表加索引
线上大表直接ALTER TABLE可能锁表或者占用大量 IO。主流方案是 Online DDL、gh-ost 或 pt-osc。
- MySQL 5.6 之后 Online DDL 支持 COPY、INPLACE 和 INSTANT 算法。
- gh-ost 通过 binlog 同步变更,不锁表,适合超大表。
- 面试能讲出“不是所有 DDL 都适合 INSTANT,加字段时用 INSTANT,加索引要考虑行格式和锁策略”就可以了。
6.4 第 50 问:已经有索引,为什么还慢
这是一道综合题,符合“夺命连环问”的最后一环。通常从以下几个方面排查:
- 索引没有命中,看 type 和 key。
- 命中索引但区分度低,扫描行数仍然很大,比如性别字段上的索引。
- 回表次数太多,考虑加覆盖索引。
- 深分页 offset 太大。
- 大数据量下 InnoDB 缓冲池命中率低,物理 IO 高。
- SQL 本身有排序,filesort 占用临时磁盘空间。
- 服务器层面磁盘、CPU、网络自身有问题。
回答顺序建议:先看慢日志定位,再 explain 看执行计划,再结合扫描行数和 Extra 判断是索引问题、SQL 写法问题还是数据分布问题。
7. 性能验证与调优结果评估
面试中如果谈到“我做过一次索引优化”,最好能说出优化前后怎么验证。不要只回答“加了索引所以快了”。
推荐的验证方式有三个:
第一,执行计划对比。
EXPLAIN SELECT order_id, create_time FROM order_tab WHERE user_id = 12345 AND status = 1;重点看优化前后的 type、rows、Extra 变化。
第二,会话级 profiling。
SET profiling = 1; SELECT * FROM order_tab WHERE user_id = 12345; SHOW PROFILES; SHOW PROFILE FOR QUERY 1;可以拿到执行耗时,但不要只看这一条,还要结合多次执行的平均值,避免冷热缓存干扰。
第三,索引使用情况。MySQL 提供了一张视图:
SELECT * FROM sys.schema_unused_indexes;这张视图能直接看到哪些索引从未被使用,是清理冗余索引和重复索引的重要依据。线上清理索引前,先备份、在低峰期执行,并保留回滚窗口。
8. 面试答题模板:三种高频切入方式
光记知识点不够,面试官更看重你输出结论的条理性。推荐下面三个答题模板,可以直接套用。
8.1 线上慢 SQL 排查模板
- 打开慢查询日志,抓出具体 SQL。
- 对慢 SQL 执行 EXPLAIN,看 type、key、rows、Extra。
- 如果没走索引,分析是 SQL 写法问题、联合索引顺序问题,还是根本缺索引。
- 如果走了索引但 rows 还是很大,检查回表次数、索引区分度和数据分布。
- 改写 SQL,重建或新增索引,再次 explain 验证。
- 灰度发布,对比优化前后接口耗时和数据库负载。
8.2 项目经验描述模板
说项目时不要只讲“用了 MySQL”,要讲“我做了什么”。
模板:系统在某个接口出现慢查询,平均耗时 2 秒。我定位到一条按用户和时间范围查询订单的 SQL,通过 EXPLAIN 发现没有命中索引,且存在深分页问题。我重新设计了联合索引,并把分页改成主键游标方式,同时把一个高频查询改成覆盖索引。上线后接口耗时从 2000ms 降到了 80ms,数据库 CPU 使用率下降约 20%。
这样表达有数据、有方法、有验证闭环。
8.3 概念题“解释覆盖索引”模板
先解释原理:二级索引叶子节点包含索引列和主键,选出来的列全在索引中,不用再回表。
再举一个例子:SELECT id, age FROM t WHERE age = 20,age 有索引,id 是主键,全从二级索引拿。最后补一句代价:增加索引宽度的代价是写放大和存储空间上升,所以只给核心高频查询做覆盖索引。
9. 总结:这 50 问该怎么用
MySQL 索引调优不是背题,而是理解数据存储和查询路径。50 问里最值得先吃透的是:B+ 树结构、聚簇索引与回表、最左前缀、索引失效、EXPLAIN 的 type 和 Extra、深分页优化、order by / join / count 场景、线上慢 SQL 排查模板。把这些连成一条线,面试时无论从哪里切入,都能接到下一个问题。
最容易踩的坑是只背结论,比如“函数会导致索引失效”,但说不清楚为什么,也不知道 MySQL 8.0 的函数索引是另一个方案。建议按本文顺序,先画一张索引结构图,再亲手执行一次 EXPLAIN,最后在测试库模拟一条慢 SQL 做完整优化。这一步做完,那些看起来“夺命”的连环问,其实只是同一条知识链上的不同节点。