一条慢 SQL 引发的连锁反应:接口性能优化记录

Cold 发布于 2020-08-22 7 次阅读 509 字 预计阅读时间: 2 分钟 后端开发


用户反馈"订单列表越翻越慢",前两页 200ms,翻到十几页要 6 秒以上。一开始以为是数据量大了,看了慢查询日志才发现是一条 SQL 写得有问题。记录一下完整的定位和优化过程。

定位

开启慢查询日志(long_query_time = 1),抓到罪魁祸首:

SELECT * FROM orders WHERE user_id = 123
ORDER BY created_at DESC LIMIT 100000, 20;

EXPLAIN 一看:type 是 index,rows 扫了 12 万。深分页时 MySQL 要把前 100020 行都取出来再丢掉前 100000 行,越往后翻代价越大。

优化第一步:延迟关联

SELECT o.* FROM orders o
JOIN (SELECT id FROM orders WHERE user_id = 123
      ORDER BY created_at DESC LIMIT 100000, 20) t
ON o.id = t.id;

子查询里只走覆盖索引(user_id + created_at + id 都在索引上,不用回表),拿到 20 个 id 再回表取整行。SQL 从 6s 降到 0.4s。

优化第二步:改产品逻辑(这才是治本)

和产品聊了一下:用户根本不会真翻到第 5000 页,这个场景是"导出全部订单"。于是接口改成游标分页(拿上一页最后一条的 id 做条件,WHERE id < 上次最大id ORDER BY id DESC LIMIT 20),每页都是常数开销;导出走异步任务。深分页问题从根上消灭。

顺带的收获

  • user_id 上居然没有单独索引,只有个联合索引把 user_id 放在第二位——等于白建。索引顺序必须匹配查询
  • SELECT * 改成按需取列,顺便解决了网络传输的开销
  • 给慢查询日志接了告警,每天超过 10 条就发邮件,别等问题大到用户来反馈

这次最大的体会:性能问题往往不只是技术问题。深分页可以用各种 SQL 骚操作续命,但"让用户不需要翻第 5000 页"才是正解。技术手段和产品手段配合,比单方面死磕 SQL 高效得多。

和喜欢的一切在一起~
最后更新于 2020-08-22