复盘一次慢 SQL 优化过程:如何用 EXPLAIN 定位问题、设计索引并验证效果。

MySQL 索引和 EXPLAIN 到底怎么学?(一次慢 SQL 优化复盘)
/ Update
4 mins
733 words
Loading views

MySQL 索引和 EXPLAIN 到底怎么学?(一次慢 SQL 优化复盘)h1

这次慢 SQL 是怎么发现的h2

当时是同一个列表接口,本地跑起来没感觉,到了测试环境数据量上来后直接变卡。

我最先犯的错是“先建索引再说”,结果加了两个索引耗时还是高。后来才老老实实按步骤排查:先看 SQL,再看执行计划,再决定索引。

这篇就是那次排查过程的原样记录。

我先把结论放前面h2

  • 不要先建索引,先定位最慢 SQL
  • EXPLAIN 看执行计划,不要靠感觉
  • 联合索引要符合最左前缀原则
  • 索引不是越多越好,写入也有成本

场景 SQLh2

SELECT id, user_id, status, amount, create_time
FROM orders
WHERE user_id = 10001
AND status = 1
ORDER BY create_time DESC
LIMIT 20;

表结构(关键字段):

CREATE TABLE orders (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
user_id BIGINT NOT NULL,
status TINYINT NOT NULL,
amount DECIMAL(10,2) NOT NULL,
create_time DATETIME NOT NULL
);

第一步:先看执行计划h2

EXPLAIN SELECT ...;

我重点看这几个字段:

  • type:访问类型,ALL 最差,ref/range/const 更好
  • key:实际使用的索引
  • rows:预估扫描行数
  • Extra:有没有 Using filesortUsing temporary

优化前(示意)h3

type: ALL
key: NULL
rows: 600000
Extra: Using where; Using filesort

这说明几乎全表扫 + 文件排序,慢是必然。

第二步:设计索引h2

这个 SQL 的过滤条件是 user_id + status,排序字段是 create_time,所以我建了联合索引:

CREATE INDEX idx_user_status_ctime
ON orders(user_id, status, create_time);

为什么这样排顺序h3

  • 先放过滤性较强且等值匹配字段
  • 再放排序字段,尽量减少额外排序

第三步:再次 EXPLAINh2

优化后(示意)h3

type: ref
key: idx_user_status_ctime
rows: 200
Extra: Using where

rows 从 60 万降到几百,接口耗时明显下降。

我踩过的坑(很典型)h2

坑 1:在索引列上做函数运算h3

-- 不推荐
WHERE DATE(create_time) = '2026-03-01'

这样可能导致索引失效。改成范围查询更稳:

WHERE create_time >= '2026-03-01 00:00:00'
AND create_time < '2026-03-02 00:00:00'

坑 2:联合索引顺序写反h3

我一开始建的是 (status, user_id, create_time),实际业务里 user_id 过滤更强,应该放前面。

坑 3:盲目给每个字段都建索引h3

读性能可能会上去,但写入(insert/update)会变慢,还会增加存储成本。

这套顺序我后来一直在复用h2

  1. 抓到慢 SQL(日志 / 慢查询)
  2. 单独执行并记录耗时
  3. EXPLAINtype/key/rows/Extra
  4. 针对 where + order by 设计索引
  5. 再次 EXPLAIN + 回归测试

面试里我会这样讲h2

可以按这个结构讲:

  • 问题:订单列表在大数据量下响应慢
  • 分析EXPLAIN 显示全表扫描 + 文件排序
  • 方案:新增联合索引 (user_id, status, create_time)
  • 结果:扫描行数显著下降,接口耗时下降

重点不是“我背过索引知识点”,而是“我能把问题定位到 SQL,再用数据验证优化有没有生效”。

评论