Skip to content
MySQL 建模与查询优化 · 第 6 篇 / 共 6 篇
领域数据与中间件
专题MySQL 专题
当前序列MySQL 建模与查询优化
阅读位置第 6 篇 / 共 6 篇当前专题第 1 个序列 / 共 7 个序列

MySQL 慢 SQL 优化案例:订单列表查询为什么从 2 秒降到 20 毫秒

慢 SQL 最容易让人误判的地方在于:

  • 明明加了索引
  • SQL 也不复杂
  • 数据量一上来还是慢得明显

真正的问题,很多时候不在“有没有索引”,而在:

  • 索引是不是和筛选、排序、分页路径一致

场景

订单后台有一个列表页,支持按下面条件查询:

  • 用户 ID
  • 订单状态
  • 创建时间范围
  • id desc 排序
  • 翻到很后面的页数

最开始这条 SQL 在数据量还小的时候没问题,后面单表到了千万级后,耗时开始稳定在 1 到 2 秒。

先说结论

这类慢 SQL 的高频根因通常是三件事叠在一起:

  1. 深分页导致扫描量变大
  2. 筛选条件和排序条件没有走同一条索引路径
  3. 先扫很多行,再回表拿数据

所以优化重点通常不是“继续加更多索引”,而是:

  • 先收敛扫描范围
  • 再让排序更顺着索引走
  • 最后减少回表

一、第一眼看 SQL,不要急着改

先确认:

  • 查询条件是否稳定
  • 排序字段是否固定
  • 是否真的需要翻很深的页
  • 是否一次查了太多列

很多列表查询问题,本质上不是数据库不行,而是页面交互设计本身就让 SQL 很难快。

二、先看 Explain 暴露了什么

这类 SQL 的典型信号通常是:

  • rows 很大
  • Extra 里出现 Using filesort
  • 走到了不理想的索引

如果条件中有:

  • user_id
  • status
  • create_time

但最终排序又是 id desc,那就要特别警惕:

  • 过滤和排序可能没走到同一棵索引上

这时数据库常常只能:

  • 先找一批候选数据
  • 再额外排序
  • 再跳过前面大量结果

三、为什么深分页特别伤

很多人会忽略这一点。

如果你要查第 5000 页,每页 20 条,数据库通常不是直接“定位到那 20 条”,而是很可能要先跳过前面大量记录。

所以深分页带来的问题通常是:

  • 扫描行数大
  • 排序成本高
  • 回表次数多

这也是为什么很多后台列表在翻到后面页数时会突然变慢。

四、这类 SQL 更实用的优化顺序

1. 优先改分页方式

如果是后台场景,很多时候没必要支持无限深翻页。

更稳妥的方案通常是:

  • 用上一页最后一条记录做游标翻页
  • 或限制最大翻页深度

这样做的收益往往比单纯调索引更直接。

2. 让筛选和排序尽量共用索引路径

索引设计时要优先围绕高频查询来做。

比如一个高频查询长期都是:

  • 按用户
  • 按状态
  • 按时间范围
  • 再按主键倒序

那索引就不能只考虑单列条件,而要考虑:

  • 查询实际是怎么筛
  • 结果最终怎么排

3. 列表页不要一上来查太多列

很多列表页其实只展示:

  • 订单号
  • 状态
  • 金额
  • 时间

但 SQL 却把大字段、扩展字段一起查出来了。

这会让回表成本被放大。

4. 把“查 ID 列表”和“查详情字段”拆开

这是一种很常见也很实用的思路。

先让第一步查询只负责:

  • 快速定位主键 ID

第二步再按 ID 批量查详情。

这样做的好处是:

  • 第一段查询更容易吃到索引优势
  • 第二段回表是有限、可控的

五、一个典型优化过程

假设最开始的现象是:

  • Explain 扫描几十万行
  • 存在 Using filesort
  • 深分页时 RT 很高

优化顺序可以是:

  1. 先限制深分页
  2. 再重做组合索引,让条件和排序更贴合
  3. 列表页改成先查 ID 再补详情

很多时候只做完前两步,耗时就会出现数量级下降。

六、这类问题最容易踩的坑

1. 看到慢就继续堆索引

索引不是越多越好。

如果索引设计不围绕高频查询路径,最后可能只是:

  • 写入更慢
  • 维护更重
  • Explain 还是不理想

2. 只盯单次 SQL,不看页面交互

如果页面允许无限深分页,数据库再怎么优化也有上限。

3. 只看有没有走索引,不看扫描量

有索引不代表就快。

真正要看的是:

  • 扫了多少行
  • 有没有额外排序
  • 回表重不重

七、怎么把经验沉淀成长期规则

更稳妥的做法通常是:

  • 后台列表默认限制最大翻页深度
  • 高频筛选页按真实查询模型设计组合索引
  • 列表页和详情页的数据读取职责拆开
  • 慢 SQL 通过监控和 Explain 定期回看

一句话总结

订单列表从 2 秒降到 20 毫秒,这类优化最核心的不是“加了索引”这么简单,而是把:

  • 深分页
  • 排序路径
  • 扫描行数
  • 回表成本

这四件事一起收拾干净。

延伸阅读相关文章优先当前专题,再补跨专题关联。
同一序列 · 回看前文会更完整SQL 开窗函数入门适合先把字段设计、索引、Explain、分页和慢 SQL 这条主线走顺。MySQL 专题 · MySQL 建模与查询优化同一序列 · 回看前文会更完整MySQL 分页优化适合先把字段设计、索引、Explain、分页和慢 SQL 这条主线走顺。MySQL 专题 · MySQL 建模与查询优化同专题其他序列 · 共享标签:案例排障MySQL 常见连接与包大小错误排查适合把事务锁、死锁、主从复制、写后读一致性和切换治理串起来看。MySQL 专题 · MySQL 事务、锁与高可用同专题其他序列 · 共享标签:案例排障MySQL 死锁怎么排查适合把事务锁、死锁、主从复制、写后读一致性和切换治理串起来看。MySQL 专题 · MySQL 事务、锁与高可用跨专题关联 · 同场景:线上排障磁盘打满后为什么删除文件不一定立刻生效适合把 Redis 抖动、MySQL 连接打满、MQ 积压、ES 发黄、ClickHouse 合并堆积和磁盘打满放在同一条基础设施排障主线上看。数据与基础设施排障专题 · 数据与基础设施故障的分层排查跨专题关联 · 同场景:线上排障大 Header、buffer、timeout 问题怎么排查适合把 location 匹配、缓冲区和负载均衡放在一起看。容器与站点部署专题 · Nginx 进阶路由与代理治理
继续阅读MySQL 建模与查询优化当前序列第 6 篇 / 共 6 篇当前专题第 1 个序列 / 共 7 个序列
往前看
上一篇SQL 开窗函数入门回到当前序列上一章上一序列设计模式与结构抽象从第 1 篇开始:设计模式总览
往后看
下一序列MySQL 事务、锁与高可用从第 1 篇开始:MySQL 事务与锁

把零散经验整理成可查、可复用、可持续更新的企业级知识门户