第四篇(实战): 订单表索引设计实战:从慢 SQL 到毫秒级

一、为什么后端一定要会"慢 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 能不能再写短一点"

而是:

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

相关推荐
JZC_xiaozhong2 小时前
电商ERP如何同步订单数据到MySQL?集成方案解析
数据库·mysql·数据分析·etl工程师·嵌入式实时数据库·电商erp集成·数据集成与应用集成
知识分享小能手2 小时前
Oracle 19c入门学习教程,从入门到精通, Oracle 表空间与数据文件管理详解(9)
数据库·学习·oracle
それども2 小时前
@ModelAttribute vs @RequestBody
java
zhengfei6112 小时前
Chroma DB — 未经授权的信息披露
数据库
雨中飘荡的记忆3 小时前
深度详解Spring Context
java·spring
Tao____3 小时前
JAVA开源物联网平台
java·物联网·mqtt·开源·ruoyi
KaiwuDB3 小时前
KaiwuDB 获评“2025 中国大数据产业年度国产化优秀代表厂商”
数据库
yqd6663 小时前
SpringSecurity的使用
java·spring
仙俊红3 小时前
Java Map 家族核心解析
java·开发语言