☰
MySQL索引调优50问:从B+树到回表与EXPLAIN全解析
2026/10/27 18:09:03 网站建设 项目流程

数据库面试题里,MySQL 索引调优是永远绕不开的高频板块。不管是校招还是社招,只要简历里写了 MySQL,面试官大概率会从“B+树”一路追到“分页优化”:聚簇索引、回表、最左前缀、索引失效、EXPLAIN、MRR、索引下推……网上很多面经内容零散,背完上一题忘下一题,核心原因是没有形成体系。

这篇内容把数据库 mysql 索引调优常见的面试题完整盘点成 50 问,分成索引基础、索引失效、EXPLAIN 分析、优化实战、进阶原理五个维度。不只是列题目,每个重点问题都给了可直接用于面试的简答思路和加分点。第一篇先说清楚:MySQL 为什么选 B+ 树、什么是回表、哪些写法会让索引失效、EXPLAIN 到底怎么看、分页排序怎么优化、线上慢 SQL 怎么排查。准备 Java 后端、测试、运维方向面试的同学,或者想系统补 MySQL 索引知识的开发者,建议直接收藏备用。

1. 索引调优 50 问速览:先建立完整知识地图

面试中的“夺命连环问”,表面是考知识点,实际是考知识结构。我先用 5 张表把 50 问的覆盖范围列出来,后续章节再按重点逐个展开。

1.1 索引基础篇(第 1-10 问)

编号面试题高频度
1InnoDB 为什么选择 B+ 树作为索引结构?极高
2为什么不用红黑树、哈希表或 B 树?高
3聚簇索引和非聚簇索引有什么区别?极高
4InnoDB 二级索引的叶子节点存的是什么?极高
5什么是回表?如何避免回表?极高
6覆盖索引为什么快?极高
7为什么建议使用自增主键?UUID 主键有什么影响?高
8普通索引和唯一索引在查询和更新上有什么区别?中
9哪些列适合建索引?哪些列不适合?高
10联合索引的字段顺序怎么定?极高

1.2 索引失效场景篇(第 11-20 问)

编号面试题高频度
11什么是最左前缀原则?极高
12在索引列上使用函数为什么会导致失效?高
13隐式类型转换为什么会导致失效?高
14字符串字段和字符集不一致会导致失效吗?中
15OR 查询一定不走索引吗?高
16LIKE 模糊查询什么时候能走索引?极高
17NOT IN、!=、IS NULL 会走索引吗?高
18哪些常见写法让联合索引只命中了一部分?极高
19为什么优化器不一定选择索引?直方图有什么用?中
20索引下推是什么?怎么证明它生效了?高

1.3 EXPLAIN 与慢查询篇(第 21-30 问)

编号面试题高频度
21EXPLAIN 主要看哪些列?极高
22type 字段从好到差怎么排?极高
23key_len 怎么计算?高
24Extra 出现 Using filesort 说明什么?高
25Using temporary 说明什么?中
26Using index 和 Using index condition 有什么区别?高
27怎么判断一条 SQL 是否发生了回表?高
28rows 扫描行数怎么理解?中
29慢查询日志怎么开启和解读?高
30批量写入时索引对性能有哪些影响?中

1.4 优化实战篇(第 31-40 问)

编号面试题高频度
31LIMIT 分页 offset 很大为什么慢?怎么优化?极高
32ORDER BY 排序怎么走索引?极高
33GROUP BY 分组怎么优化?高
34COUNT(*) 为什么慢?怎么优化?高
35JOIN 查询怎么优化?驱动表怎么选?极高
36大表加索引要注意什么?高
37为什么索引不是越多越好?高
38冗余索引和重复索引怎么识别清理?中
39如何查看表上每个索引的使用情况?中
40优化器选错索引怎么办?中

1.5 进阶原理篇(第 41-50 问)

编号面试题高频度
41什么是 MRR?它解决了什么问题?中
42什么是索引合并 index merge?中
43change buffer 对二级索引有什么影响?中
44普通索引和唯一索引在 change buffer 上有什么不同?中
45自适应哈希索引是什么?需要人工建吗?低
46MVCC 下走索引查询读到的是快照还是最新数据?高
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 排查模板

  1. 打开慢查询日志,抓出具体 SQL。
  2. 对慢 SQL 执行 EXPLAIN,看 type、key、rows、Extra。
  3. 如果没走索引,分析是 SQL 写法问题、联合索引顺序问题,还是根本缺索引。
  4. 如果走了索引但 rows 还是很大,检查回表次数、索引区分度和数据分布。
  5. 改写 SQL,重建或新增索引,再次 explain 验证。
  6. 灰度发布,对比优化前后接口耗时和数据库负载。

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 做完整优化。这一步做完,那些看起来“夺命”的连环问,其实只是同一条知识链上的不同节点。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询