三亩地 三亩地SAN MU DI · CODE DIARY
ARTICLE DETAIL

日记详情

真实记录编程学习的某一天,欢迎挑你感兴趣的翻一翻。

一条慢查询毁了整个接口,我用三步把它救活了

一条慢查询毁了整个接口,我用三步把它救活了

一条慢查询毁了整个接口,我用三步把它救活了

说真的,每次线上系统突然卡顿,排查到最后十有八九都是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

作者郑重声明,本文内容为本人原创文章,纯净无利益纠葛,如有不妥之处,请及时联系修改或删除。诚邀各位读者秉持理性态度交流,共筑和谐讨论氛围~

← 返回列表