一条慢查询毁了整个接口,我用三步把它救活了
一条慢查询毁了整个接口,我用三步把它救活了
说真的,每次线上系统突然卡顿,排查到最后十有八九都是SQL的锅。我曾经为了优化一条订单查询语句,从执行计划看了三天,把索引改了五版,最后才发现问题根本不在索引上。今天这篇文章,我就拿一个真实的慢查询案例,手把手带你走一遍SQL调优的完整思路,保证你看完之后,再遇到慢查询心里就有底了。
一、问题的发现:一条SQL拖垮了整个接口
事情是这样的,我们有一个订单列表查询接口,平时响应时间在50毫秒左右,某天上午突然飙到了3秒以上,用户那边直接报超时了。
先把这条SQL贴出来,大家感受一下:
sql
SELECT
o.id, o.order_no, o.user_id, o.total_amount, o.status,
u.nickname, u.phone
FROM order o
LEFT JOIN user u ON o.user_id = u.id
WHERE o.status IN (1, 2, 3)
AND o.create_time >= '2025-01-01 00:00:00'
AND o.create_time <= '2025-12-31 23:59:59'
ORDER BY o.create_time DESC
LIMIT 20;
这条SQL看起来非常普通对吧?但它的执行时间从原来的几十毫秒直接飙到了2秒多,这背后到底发生了什么?
☆ 关键线索:order表数据量大约有800万条,user表大约有120万条。
二、第一步:用Explain看透执行计划
调优的第一步永远是看执行计划,别上来就改索引,那是瞎蒙。
sql
EXPLAIN SELECT
o.id, o.order_no, o.user_id, o.total_amount, o.status,
u.nickname, u.phone
FROM order o
LEFT JOIN user u ON o.user_id = u.id
WHERE o.status IN (1, 2, 3)
AND o.create_time >= '2025-01-01 00:00:00'
AND o.create_time <= '2025-12-31 23:59:59'
ORDER BY o.create_time DESC
LIMIT 20;
执行结果如下:
id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE o ALL idx_status,idx_create_time NULL NULL NULL 7823456 Using where; Using filesort
1 SIMPLE u eq_ref PRIMARY PRIMARY 8 o.user_id 1 NULL
一看就炸了。order表的type是ALL,意思是全表扫描,扫描了将近782万行数据。Extra里写着Using filesort,说明排序没有走索引,额外做了文件排序。
这就是慢的根源:全表扫描 + 文件排序,双重暴击。
三、第二步:分析瓶颈,制定调优策略
先别急着加索引,我们得想清楚几个问题:
1、WHERE条件里有status和create_time两个字段,到底该建什么索引?
2、ORDER BY create_time DESC,排序字段能不能和索引配合?
3、LIMIT 20说明只要前20条,能不能利用这个特性优化?
☆ 核心思路:既然查询有范围条件 + 排序 + 分页,最理想的情况是建一个联合索引,让MySQL能直接按索引顺序取数据,避免全表扫描和文件排序。
四、第三步:构建最优索引并验证效果
根据上面的分析,我建了一个联合索引:
sql
ALTER TABLE `order`
ADD INDEX idx_status_create_time (status, create_time);
为什么是(status, create_time)而不是反过来?这里有个很重要的原则:
1、等值条件放前面,范围条件放后面。 status IN (1,2,3)虽然是多值,但本质上还是等值匹配,应该放在联合索引的最左边。create_time是范围查询,放在后面。
2、排序字段要能接上索引。 因为ORDER BY create_time DESC,而索引里create_time在第二位,当status确定之后,create_time本身是有序的,MySQL可以直接按索引顺序往后读,不需要额外排序。
再看一次执行计划:
id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE o range idx_status_create_time idx_status_create_time 12 NULL 156842 Using index condition
1 SIMPLE u eq_ref PRIMARY PRIMARY 8 o.user_id 1 NULL
效果立竿见影:
1、type从ALL变成了range,走了索引范围扫描。
2、扫描行数从782万降到了15万左右,降了将近50倍。
3、Extra里Using filesort消失了,排序直接走索引。
执行时间从2.3秒直接降到了45毫秒。
五、第四步:还能不能再优化?深挖一下
到这里其实已经够用了,但作为一个有追求的人,我又多想了一步。
☆ 问题:status IN (1,2,3)匹配的数据有15万行,但我们只要最新的20条。MySQL虽然走了索引,但还是要扫描15万行来找到这20条,有没有办法让它只扫描20行?
答案是:改业务逻辑,用子查询先定位,再关联。
sql
SELECT
o.id, o.order_no, o.user_id, o.total_amount, o.status,
u.nickname, u.phone
FROM (
SELECT id, order_no, user_id, total_amount, status
FROM `order`
WHERE status IN (1, 2, 3)
AND create_time >= '2025-01-01 00:00:00'
AND create_time <= '2025-12-31 23:59:59'
ORDER BY create_time DESC
LIMIT 20
) o
LEFT JOIN user u ON o.user_id = u.id;
这个改写的核心在于:先在order表上利用索引直接取出最新的20条ID,然后再去关联user表。这样order表的扫描行数就从15万降到了20行。
再看执行计划:
id select_type table type possible_keys key key_len ref rows Extra
1 PRIMARY ALL NULL NULL NULL NULL 20 NULL
1 PRIMARY u eq_ref PRIMARY PRIMARY 8 o.user_id 1 NULL
2 DERIVED order index idx_status_create_time idx_status_create_time 12 NULL 20 Using where
看到没?order表的rows直接变成了20,type变成了index,说明完全按索引顺序在取数据,一行多余的都没扫。
执行时间进一步降到了8毫秒。
六、几个容易踩坑的点,提前避一避
1、☆ 联合索引的字段顺序不能乱。很多人喜欢把create_time放前面,觉得时间范围查询很常见。但一旦create_time在第一位,status就用不上索引了,因为范围查询之后的字段索引失效。这是MySQL最经典的"最左前缀"规则。
2、☆ IN和=在索引使用上是一样的,都算等值匹配。但如果IN里面的值特别多,比如IN (1,2,3,4,5,6,7,8,9,10),MySQL可能会认为不如全表扫描,这时候可以考虑用UNION ALL改写。
3、☆ LEFT JOIN的时候,右表一定要有索引。上面的例子里user.id是主键,天然有索引,所以没问题。但如果右表的关联字段没有索引,那又是一个大坑。
4、☆ LIMIT优化一定要配合子查询使用,不然MySQL还是会先找到所有符合条件的行,再取前N条。这个技巧在数据量大的时候特别管用。
七、总结一下这次调优的完整思路
步骤 操作 效果
1 用Explain看执行计划 发现全表扫描 + 文件排序
2 分析WHERE条件和排序字段 确定索引字段和顺序
3 建联合索引(status, create_time) 扫描行数从782万降到15万
4 用子查询改写,先取ID再关联 扫描行数从15万降到20
最终结果 执行时间从2.3秒降到8毫秒 提升约280倍
说白了,SQL调优这事儿没有什么黑魔法,就是三步:看计划、建对索引、改写SQL。但每一步都需要你真正理解MySQL是怎么执行查询的,而不是靠感觉瞎猜。
希望这篇文章能帮你在遇到慢查询的时候,少走一些弯路。毕竟,生产环境的每一秒卡顿,背后可能都是一条没优化好的SQL在作妖。
💡注意:本文所介绍的软件及功能均基于公开信息整理,仅供用户参考。在使用任何软件时,请务必遵守相关法律法规及软件使用协议。同时,本文不涉及任何商业推广或引流行为,仅为用户提供一个了解和使用该工具的渠道。
你在生活中时遇到了哪些问题?你是如何解决的?欢迎在评论区分享你的经验和心得!
希望这篇文章能够满足您的需求,如果您有任何修改意见或需要进一步的帮助,请随时告诉我!
感谢各位支持,可以关注我的个人主页,找到你所需要的宝贝。
博文入口:https://blog.csdn.net/Start_mswin 复制到【浏览器】打开即可,宝贝入口:https://pan.quark.cn/s/b42958e1c3c0 宝贝:https://pan.quark.cn/s/1eb92d021d17
作者郑重声明,本文内容为本人原创文章,纯净无利益纠葛,如有不妥之处,请及时联系修改或删除。诚邀各位读者秉持理性态度交流,共筑和谐讨论氛围~
