✅ 精简版
顺序:
完整排查步骤
1. 捕获慢 SQL(第一步,找到是哪条 SQL)
- 开启 MySQL慢查询日志 slow_query_log,设置阈值比如 long_query_time=1s,超过 1 秒自动记录。
- 云数据库(RDS)直接后台看慢 SQL 报表。
- 也可以通过 show processlist 实时看当前正在执行的长耗时 SQL。
注意:show processlist 只能看到正在运行的,已经执行完的要看慢日志。
重点看四个指标:执行时间/扫描行数/返回行数/排序、临时表或者锁等待
如果扫描了几百万行却返回了十几行,可能是过滤条件没有索引;
如果扫描的数据不多但是扫描时间很长,可能是锁等待。
2. explain 执行计划(最核心,必讲)
执行 explain select xxx,重点看这几个字段:
- ALL:全表扫描,大表绝对红线,必须优化
- range:范围查询,比如 where id>100,尚可,注意范围不要过大
- ref:普通索引等值匹配,正常
- Using filesort:文件排序,没用到索引排序,需要额外排序,性能差
- Using temporary:创建临时表,常见于 group by,消耗大
- Using index:索引覆盖,最佳!不需要回表
- Using where:存储引擎返回数据后,MySQL 服务层再过滤
主要看标:
1.全表扫描还是走索引?
2.是否回表过多?(比如,避免select *, 设计覆盖索引)
3.排序和分页?(比如深分页,游标分页)
索引失效常见原因(面试官最爱追问)
- 索引列做函数运算、隐式类型转换(字符串不加引号)
- like %前缀通配 like '%abc'
- or 两边有非索引字段
- 联合索引最左匹配原则不满足
- MySQL 优化器判断走索引代价更大,主动放弃索引,走全表扫描(比如筛选后数据占表 30% 以上)
什么是回表?
前提:InnoDB 索引分两种:主键索引(聚簇索引)、二级索引(普通索引)
二级索引里面存的不是完整行数据,只存【索引列 + 主键】。通过二级索引找到主键之后,再拿主键去主键索引里读取完整行数据,这个二次查找的动作就叫回表。
1. InnoDB 聚簇索引原理
InnoDB 主键索引就是聚簇索引:
- 叶子节点 = 完整一行的所有数据
- 表数据本身就放在主键索引的叶子节点,数据和主键索引绑定在一起
二级索引(普通索引、唯一索引):
- 叶子节点只存:索引字段的值 + 主键 ID,没有其他业务字段
举个例子
order 表:id(主键), orderNo, user_id, amount 建立普通索引:idx_orderNo(orderNo)
执行 SQL:
select * from `order` where orderNo = 'ORD123456';
执行过程:
回表本质:两次 B + 树查找,一次二级索引,一次主键索引,会产生额外 IO,影响性能。
2. 什么情况不会回表?—— 索引覆盖(Using index)
查询需要的所有字段,全部都包含在二级索引里面,不需要去主键索引查数据,就不会回表。
把索引改成联合索引:idx_orderNo(orderNo, user_id, amount)
select orderNo,user_id,amount from `order` where orderNo='ORD123456';
explain Extra:Using index,直接从二级索引拿到全部需要字段,不需要回表。
3. 什么时候回表代价很高?
- 查询出来结果集很大,大量行都要回表,大量随机 IO
- 这也是 MySQL 优化器有时候有索引但是不走索引的原因: 如果筛选出大量数据,回表太多,优化器评估:直接全表顺序扫描比大量随机 IO 回表更快,于是放弃索引。
✅精简版
InnoDB 主键索引是聚簇索引,叶子节点保存完整行数据;
二级索引叶子节点只存索引列 + 主键。
当我们通过二级索引查询,需要的字段不在二级索引里面,就需要拿着主键去主键索引再次查找完整数据,这个过程叫回表。
如果查询的所有字段都在二级索引里,就是索引覆盖,避免回表,性能更好。
高频追问
Q1:主键查询会不会回表?
A:不会。主键直接走聚簇索引,一次 B + 树查找直接拿到完整行,不存在回表。
Q2:联合索引什么时候触发回表?
A:where 条件命中索引,但是select要查询的字段不在联合索引里面,就需要回表。
Q3:回表是随机 IO 还是顺序 IO?
A:随机 IO。二级索引查出来的主键大概率是离散无序的,去主键索引读取数据的时候,磁盘指针到处跳,随机 IO 开销远高于顺序扫描。
Q4:为什么 MyISAM 没有回表概念?
A:MyISAM 是非聚簇索引,主键索引和二级索引结构一样,叶子节点存的都是磁盘文件偏移地址,所有索引查找都是拿偏移量读文件,没有聚簇索引这个概念,所以不存在回表。
3. 排查锁 (锁等待、死锁)& 长事务
SQL 本身逻辑很快,但是被锁住,表现为查询卡住
show engine innodb status;
- 长事务:事务开启很久不提交,占用行锁,阻塞后面的 DML / 查询(RR 隔离级别下 MVCC 读快照,普通 select 不加行锁;update/delete 会被阻塞)
注意:普通快照读不会被行锁阻塞;当前读(select … for update)会被行锁阻塞。
- 存在表锁、意向锁,大量事务等待锁,出现锁等待、事务回滚。
4. 服务器资源层面
- CPU:MySQL CPU 100%,大概率大量排序、全表扫描、复杂计算
- IO:磁盘 IO 打满,大量随机 IO,回表过多、缺少索引
- 内存:buffer pool 太小,热点数据放不到内存,大量磁盘读取
5. 业务 SQL 写法问题
6. 优化之后验证
优化后再次 explain,观察 type、rows;上线前压测,监控 QPS、RT、慢查询数量变化。
高频追问(背)
Q1:Using filesort 一定很差吗?
不一定。小数据量 filesort 影响很小;大表、大结果集出现 filesort 才是严重问题。本质是排序字段没有索引,MySQL 要在内存 / 磁盘做排序。
Q2:select * 有什么坏处?
规范:只查业务需要的字段。
Q3:联合索引最左匹配原则是什么?
联合索引 idx_a_b_c(a,b,c),查询条件必须从左开始连续匹配。 where a=? ✔ where a=? and b=? ✔ where b=? ❌ 跳过左边 a,索引失效 范围查询后面的字段不能走索引:where a=? and b>10 and c=?,c 无法使用索引。
Q4:什么是索引覆盖?
查询需要的所有列,全部都在索引里面,不需要回主键索引查原始数据,Extra 显示 Using index,性能很高。 比如联合索引包含 select 后面所有字段 + where 条件字段。
Q5:为什么有时候有索引,但是 MySQL 不走索引?
MySQL 优化器会估算成本:如果这条 SQL 查询结果占表数据很大比例(比如 > 20%~30%),走索引需要大量回表随机 IO,优化器判断全表顺序扫描更快,主动放弃索引。
Q6:慢查询日志开启对性能影响大吗?
有一点点开销,线上生产环境一般可以开启,阈值合理设置(1s),只记录慢 SQL,不会大量写入。
Q7:MVCC 和锁的关系?普通 select 会不会被行锁阻塞?
InnoDB RR 隔离级别,普通 select 是快照读,读取历史版本,不加行锁,不会被行锁阻塞; select … for update /select … lock in share mode 是当前读,会加锁,会被阻塞。





