慢 SQL 排查与优化流程

一句话

慢 SQL 不要上来就加索引,而是先定位慢点,再用慢查询日志和 EXPLAIN 分析原因,最后从索引、SQL 写法、返回字段、扫描范围、分页和缓存等方向针对性优化,并用实际耗时验证。


1. 排查慢 SQL 的正确顺序

用户反馈"页面慢",不一定是 SQL 慢,可能是:

text 复制代码
接口鉴权慢
远程调用慢
循环里多次查库
序列化数据太大
网络慢
SQL 本身慢

所以排查顺序应该是:

text 复制代码
接口日志 / 链路追踪
  ↓
确认是否 SQL 慢
  ↓
慢查询日志定位具体 SQL
  ↓
EXPLAIN 分析执行计划
  ↓
针对原因优化
  ↓
验证优化效果

2. 第一步:先定位慢在哪里

不要一上来就说"加索引"。

先确认:

text 复制代码
接口总耗时多少?
SQL 执行耗时多少?
是否有远程调用?
是否循环查库?
是否返回数据过大?

分工可以这样理解:

工具 作用
应用日志 / APM / 链路追踪 判断接口慢在哪里
慢查询日志 定位数据库里具体哪条 SQL 慢
EXPLAIN 分析这条 SQL 为什么慢

3. EXPLAIN 重点看哪些字段?

字段 重点含义 判断
type 访问类型 ALL 通常代表全表扫描,较差
key 实际使用的索引 NULL 表示没选索引
rows 预估扫描行数 越大越可能慢,不是最终返回行数
filtered 过滤比例 过滤比例低说明扫了很多无效数据
Extra 额外信息 关注 Using filesortUsing temporaryUsing index

示例:

text 复制代码
type = ALL
key = NULL
rows = 500000
Extra = Using where; Using filesort

说明:

text 复制代码
全表扫描 + 没走索引 + 预估扫描 50 万行 + 额外排序

这条 SQL 慢的核心问题就是:

text 复制代码
扫描行数太多 + 没走索引 + 排序没有利用索引

4. 示例 SQL:怎么设计联合索引?

SQL:

sql 复制代码
SELECT *
FROM exam_record
WHERE user_id = ?
  AND status = ?
  AND create_time BETWEEN ? AND ?
ORDER BY create_time DESC
LIMIT 20;

条件类型:

text 复制代码
user_id = ?                      等值条件
status = ?                       等值条件
create_time BETWEEN ? AND ?      范围条件
ORDER BY create_time DESC        排序字段

适合的联合索引:

sql 复制代码
(user_id, status, create_time)

原因:

text 复制代码
等值条件放前面,范围 / 排序字段放后面。

这样可以先通过 user_idstatus 缩小范围,再按 create_time 范围扫描,并尽量利用索引顺序完成排序。


5. filesort 和回表不是一回事

filesort

关注的是:

text 复制代码
排序是否能利用索引顺序?

如果索引是:

sql 复制代码
(user_id, status, create_time)

查询条件:

sql 复制代码
WHERE user_id = ? AND status = ?
ORDER BY create_time DESC

通常可以利用索引顺序,避免额外 filesort

回表

关注的是:

text 复制代码
查询结果字段是否被索引覆盖?

如果是:

sql 复制代码
SELECT *

即使 WHERE 和 ORDER BY 走了联合索引,也通常需要回表取整行数据。

如果只查:

sql 复制代码
SELECT id, user_id, status, create_time

并且索引是:

sql 复制代码
(user_id, status, create_time)

在 InnoDB 里二级索引叶子节点带主键 id,可能形成覆盖索引,减少回表。


6. 有索引为什么还慢?

记住:

text 复制代码
有索引 ≠ 一定快
走索引 ≠ 扫描行数一定少

可能原因:

  1. 命中范围太大,rows 仍然很高
  2. 字段选择性差,比如 status=已完成 占 90%
  3. 时间范围太大,比如一次查一年
  4. SELECT * 导致大量回表
  5. 深分页导致扫描和丢弃大量数据
  6. 统计信息不准,优化器选错索引
  7. SQL 写法导致索引失效

7. 什么是选择性?

选择性可以理解为:

text 复制代码
一个条件能筛掉多少数据。
条件 选择性
user_id = 1001 通常较高
status = 已完成 通常较低
create_time = 今天 较高
create_time BETWEEN 一年前 AND 今天 较低

索引设计时,通常希望高选择性的字段靠前,但也要结合等值、范围、排序和实际查询模式综合判断。


8. 最左前缀与不同查询场景

索引:

sql 复制代码
(user_id, status, create_time)

适合查询 A:

sql 复制代码
WHERE user_id = ?
  AND status = ?
ORDER BY create_time DESC
LIMIT 20;

不太适合查询 B:

sql 复制代码
WHERE status = ?
ORDER BY create_time DESC
LIMIT 20;

原因:查询 B 跳过了最左列 user_id,无法充分利用联合索引。

对于查询 B,可能需要单独设计:

sql 复制代码
(status, create_time)

结论:

不是一个大联合索引解决所有问题,而是按高频查询场景设计索引。


9. 常见优化方向

问题 优化方向
没走索引 设计合适索引,调整 SQL 条件
联合索引顺序不合理 等值在前,范围 / 排序在后
回表太多 减少 SELECT *,尝试覆盖索引
排序慢 让 ORDER BY 字段匹配索引顺序
扫描范围大 缩小时间范围,提高筛选条件选择性
深分页 延迟关联、基于游标分页、记录上次最大/最小 ID
索引失效 避免函数、隐式类型转换、前导 %、违反最左前缀
热点查询 加缓存,但要考虑一致性和过期策略

10. 索引失效检查清单

常见索引失效场景:

text 复制代码
1. 不满足最左前缀原则
2. 对索引列使用函数,如 DATE(create_time)
3. 隐式类型转换,如 VARCHAR 字段传数字
4. LIKE 以 % 开头
5. OR 条件中有非索引字段
6. 排序字段和索引顺序不匹配

🎙 面试回答模板

text 复制代码
如果项目里遇到一条慢 SQL,我一般会先定位慢点,不会一上来就直接加索引。首先通过接口日志、链路追踪或者 SQL 耗时日志,判断是接口整体链路慢,还是数据库查询慢。如果确认是 SQL 慢,再通过慢查询日志定位具体 SQL。拿到 SQL 后,我会用 EXPLAIN 看执行计划,重点关注 type、key、rows、filtered 和 Extra,判断有没有走索引、是否全表扫描、预估扫描行数是否过大、是否出现 Using filesort 或 Using temporary。

优化时会结合查询条件设计合适的联合索引,比如等值条件放前面,范围和排序字段放后面;同时避免 SELECT *,尽量使用覆盖索引减少回表。如果已经走了索引但仍然慢,我会继续看是不是时间范围太大、字段选择性太低、分页过深,或者数据量本身太大。最后还要检查 SQL 写法是否导致索引失效,比如函数计算、隐式类型转换、前导模糊 LIKE、不符合最左前缀原则等。优化后再通过 EXPLAIN 和实际接口耗时对比验证效果。

参考来源