一、为什么后端一定要会“慢 SQL 实战”

学索引、学 B+Tree,如果不能落到一个真实 SQL 上,本质上都是“概念”。

真实项目里,慢 SQL 往往长这样:

  • 功能很正常
  • 代码没报错
  • 数据一多,接口直接炸

所以这一篇不讲抽象理论,只做一件事:

👉 带你完整走一遍:发现慢 SQL → explain → 判断瓶颈 → 改索引 + 改 SQL → 再 explain 验证。

二、真实业务场景

一张订单表:

t_order
--------------------------------
id (PK)
user_id
status
amount
create_time
remark

典型业务 SQL(订单列表页):

SELECT *
FROM t_order
WHERE user_id = 123
  AND status = 1
ORDER BY create_time DESC
LIMIT 20 OFFSET 2000;

业务含义:

👉 查询某个用户的已完成订单,按时间倒序,翻到第 101 页。

数据量:

👉 t_order 表 500 万行
👉 user_id = 123 大约 10 万条订单

现象:

👉 页面明显卡
👉 接口 RT 几百 ms ~ 几秒

三、第一步:EXPLAIN 看清数据库在干嘛

EXPLAIN
SELECT *
FROM t_order
WHERE user_id = 123
  AND status = 1
ORDER BY create_time DESC
LIMIT 20 OFFSET 2000;

典型你会看到类似:

key: idx_user
rows: 100000
Extra: Using where; Using filesort

这三项非常关键:

  • key = idx_user
    👉 只用了 user_id 单列索引

  • rows = 100000
    👉 扫描了十万行

  • Using filesort
    👉 排序是额外做的

数据库真实在干嘛:

  1. 用 idx_user 找到 user_id = 123 的十万条
  2. 再一条条判断 status
  3. 再把结果全部排序
  4. 再跳过 2000 条
  5. 再取 20 条

慢是必然的。

四、第二步:先解决“走路方式”(联合索引)

where 条件:

  • user_id = ?
  • status = ?

order by:

  • create_time desc

所以第一版联合索引:

CREATE INDEX idx_user_status_ct
ON t_order(user_id, status, create_time);

再 explain:

key: idx_user_status_ct
rows: 3000
Extra: Using where

变化非常明显:

  • 扫描行数从 10 万 → 3000
  • 排序消失(顺着索引顺序拿)

👉 这一步解决的是:范围缩小 + 排序走索引

但还没完。

五、第三步:解决“回表成本”(字段 & 覆盖)

原 SQL 是:

SELECT *

这意味着:

👉 每一条都要回表取整行(amount / remark 等大字段)

改为:

SELECT id, status, amount, create_time
FROM t_order
WHERE user_id = 123
  AND status = 1
ORDER BY create_time DESC
LIMIT 20 OFFSET 2000;

如果列表页只需要这几个字段,就不要 *

如果你愿意进一步优化覆盖索引:

CREATE INDEX idx_user_status_ct_amt
ON t_order(user_id, status, create_time, amount);

再 explain,通常会看到:

Extra: Using index

👉 这意味着:不回表(覆盖索引)

六、第四步:真正的性能杀手 —— 深分页

即使这样,这条 SQL 还有一个结构性问题:

LIMIT 20 OFFSET 2000;

👉 offset 越大,数据库越难受
👉 本质还是:先走过前 2000 条

工程里真正推荐的是:游标分页(基于最后一条记录)

✅ 第一页

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

假设最后一条是:

👉 create_time = '2026-01-21 10:00:00'
👉 id = 880001

✅ 下一页(关键 SQL)

SELECT id, status, amount, create_time
FROM t_order
WHERE user_id = 123
  AND status = 1
  AND (
    create_time < '2026-01-21 10:00:00'
    OR (create_time = '2026-01-21 10:00:00' AND id < 880001)
  )
ORDER BY create_time DESC, id DESC
LIMIT 20;

对应索引:

CREATE INDEX idx_user_status_ct_id
ON t_order(user_id, status, create_time, id);

这条 SQL 的本质是:

👉 从索引树某个位置开始往后扫,不用再跳过前 N 条。

这一步,通常是:

👉 秒级 → 毫秒级

七、完整优化路线总结

这一条慢 SQL,实际做了四层优化:

✅ 第一层:让 where + order by 走同一棵索引

(联合索引)

✅ 第二层:减少回表

(字段收敛 / 覆盖索引)

✅ 第三层:干掉 filesort

(排序字段进索引)

✅ 第四层:干掉 offset

(游标分页)

八、后端在 explain 里真正该盯的 4 个点

每次看执行计划,优先级永远是:

1️⃣ key:走了哪棵索引
2️⃣ rows:大概扫了多少行
3️⃣ Extra:有没有 filesort / temporary / using index
4️⃣ 是否存在回表路径

九、给第五课的一句“工程总结”

👉 慢 SQL 优化,本质不是调参数,而是重构“数据的查找路径”。

你要做的不是:

“SQL 能不能再写短一点”

而是:

“数据库是不是还在替我干脏活。”

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐