先说清楚:这不是线上接口,是后台报表,月底财务点一次。跑了五年,从 1 秒多变成 4 秒多,两三个人同时点就转圈。
数据量不大:orders 12 万行,order_items 40 多万行,customers 8000 多行。三张表 JOIN,按月份和客户筛,按金额排序取前 200 行。
第一件事还是看 EXPLAIN
2016 年那次慢查询我们就是靠 EXPLAIN 弄明白的,这次一样,先把语句贴进去看执行计划。
```
EXPLAIN SELECT ... FROM orders o JOIN order_items i ON ... JOIN customers c ON ... WHERE o.month = '2025-08' AND c.region = '华北' ORDER BY o.amount DESC LIMIT 200
```
输出三行,问题全摆着:
- customers 那行 type 是 ALL,rows 8000 多,没走索引。region 字段上压根没建索引。
- 优化器把 order_items 当成了驱动表,40 多万行的表放在最外层,后面两张跟着被拖着跑。
- 最后一行 Extra 里写着 Using filesort; Using temporary。排序没吃上索引,MySQL 自己开了临时表排。
四秒多就这么来的:全表扫客户、大表当起点、结果再搬去排序。
1. 建索引,但别乱建
第一反应是给 region 加个索引。加了,从 4 秒多到 3 秒出头,还是慢。
真正要建的是联合索引,顺序得按查询怎么用来定:
- customers 上建 (region, id)。region 用来筛,id 是 JOIN 键,放后面能让回表少一点。
- orders 上原来只有 month 单列索引,改成 (month, customer_id, amount)。month 筛、customer_id 关联、amount 参与排序,三列按这个顺序排,正好覆盖 WHERE、JOIN、ORDER BY。
- order_items 上已有 (order_id) 索引,够用,没动。
这里有个先后:先想清楚「这条 SQL 按什么顺序找数据」,再决定索引列顺序。反着来,建一堆单列索引,MySQL 多半只挑一个用,剩下的白占空间。
建完再看 EXPLAIN,customers 那行从 ALL 变成 ref,rows 从 8000 多掉到 600 多。
2. 驱动表不让优化器猜了
40 多万行那张被选成驱动表,多半是统计信息旧了。跑了 ANALYZE TABLE 好一点,但月底数据一导入又会飘。
最后用了 STRAIGHT_JOIN,把 JOIN 顺序写死:先 customers 按 region 筛出几百行,再拿这些 id 去 orders 里按 (month, customer_id) 找,最后才关联明细。小结果集在前面,后面每一步都跟着变小。
这么写有代价:数据分布变了,写死的顺序可能不再最优。注释里写了「按华北占比约 7% 定的顺序」,留了开关能退回普通 JOIN。
3. 干掉 filesort
ORDER BY o.amount DESC 跟 WHERE 里的 month 不是同一列,MySQL 得单独排。
把 amount 并进 orders 那个联合索引的最后一列之后,(month, customer_id, amount) 里 amount 本身有序。但还有个坎:customer_id 是范围条件,IN 一串 id,按最左前缀,范围之后的列不能用来排序,filesort 还在。
后来改了写法:不在外层 ORDER BY,改成先按客户分组取各自的前 N 条,再在应用层合并排序。每个客户的查询都能吃满 (month, customer_id, amount) 索引,排序在索引里就完成了。应用层合并 200 行,几乎不花时间。
EXPLAIN 里 Using filesort 和 Using temporary 都消失了。
中间走过的弯路
不是一路顺的。给 orders 加过 (amount) 单列索引,想着排序快些,结果写入慢了 15%,查询没快,优化器根本没选它,回滚删掉。还试过把 LIMIT 200 挪进子查询,行数不确定,取少了,报表数字对不上,财务当天就发现。两处都记在文档里,标题写「别干的事」。
结果
四个动作做完,同一条 SQL 从 4 秒多到 0.3 秒出头,月底三个人同时点也不排队了。没分库、没加 Redis、没升配,就是两个联合索引、一行 STRAIGHT_JOIN、一处改写。
慢查询阈值顺手从 1 秒调到 0.5 秒,想早点看见下一条变慢的语句。
一句大白话
五年里没人改过这条 SQL,是数据自己长胖的。索引不是上线时建好就完事的东西,数据量翻几倍之后,得再拿 EXPLAIN 看一眼。这次要是早点看,不至于等财务来问「怎么又转圈了」。
9 月 19 号下午改完,把那张 EXPLAIN 截图贴进内部文档《两小时》旁边。两张隔着九年,毛病一样:以为以前配过就不用再看了。

