慢 SQL 并不等于 SQL 写错,真正困难的是:在生产环境中,如何快速、准确地把它找出来,并确认它就是性能问题的根因。下面这篇文章,完整复盘一次真实 MySQL 线上慢 SQL 的排查过程,重点不是工具,而是顺序和判断逻辑。
一、背景:问题是如何暴露的
某天线上系统突然报警,接口平均响应时间从 200ms 飙升到 2 秒以上,高峰期甚至出现大量超时。同时数据库 CPU 长时间维持在 80% 以上,已经明显影响正常业务。
这个时候,第一反应并不是马上去改 SQL,而是先搞清楚:问题到底是不是数据库导致的。
二、第一步:确认是否为数据库问题
排查慢 SQL 之前,我先确认了三件事。
第一,是否存在流量突增。查看监控后发现 QPS 正常,没有异常放量。
第二,是否有代码发布。确认近期没有任何上线操作。
第三,数据库资源是否异常。CPU 使用率明显升高,IO 压力增大。
综合判断,可以基本确定:这是数据库侧问题,而不是业务逻辑或流量问题。
三、第二步:快速定位慢 SQL
确认是数据库问题后,下一步不是猜,而是用数据说话。
首先检查 MySQL 是否开启了慢查询日志,并确认慢查询阈值设置合理。线上环境的 long_query_time 设置为 1 秒,意味着执行超过 1 秒的 SQL 都会被记录。
随后直接查看慢查询日志,很快发现同一条 SQL 在短时间内大量出现,执行时间在 1.6 秒到 2.3 秒之间。
这一步的目标只有一个:锁定“是哪一条 SQL”,而不是一上来就想着怎么优化。
四、第三步:问题 SQL 本身长什么样
慢查询日志中的 SQL 如下(已脱敏):
查询 orders 表,根据 user_id 和 status 过滤数据,再按 create_time 倒序排序,最后分页返回 20 条数据。
这类 SQL 在业务系统中非常常见,但在数据量较大时,也是慢 SQL 的高发区。
此时不要急着改写 SQL,而是先看执行计划。
五、第四步:使用 EXPLAIN 分析执行计划
对该 SQL 执行 EXPLAIN 后,得到几个关键信息:
type 为 ALL
扫描行数约 300 万
Extra 中出现 Using where 和 Using filesort
这三个信息已经非常致命:
ALL 说明是全表扫描,rows 表示扫描了几乎整张表,而 Using filesort 则意味着排序没有利用索引。
到这里可以明确一点:这条 SQL 的慢,不是偶然,而是结构性问题。
六、第五步:分析索引为什么没有生效
表结构中包含 user_id、status 和 create_time 字段,表上也确实存在 user_id 和 status 的单列索引。
问题在于,这条 SQL 同时存在过滤条件和排序条件,而单列索引无法同时满足 WHERE 和 ORDER BY 的需求。最终 MySQL 优化器选择了全表扫描加文件排序。
换句话说,不是没有索引,而是索引设计无法覆盖真实的查询场景。
七、第六步:制定优化方案
优化目标很明确:减少扫描行数,避免 filesort,让
要带一些格式,不然没有层次
行,这次我给你一个**“优快云 友好版”正文**:
不用 Markdown 语法、不用代码块标记,但有清晰层次、编号、空行、可直接粘贴发布,这是很多高阅读量技术博主常用的写法。
下面这篇是完整终稿,你可以直接用。
———
标题:慢 SQL 排查完整流程:从报警到定位(MySQL 生产实战)
————————————————
【一、问题背景】
某天线上系统突然报警:
接口平均响应时间从 200ms 飙升到 2 秒以上,高峰期出现大量超时;与此同时,数据库 CPU 长时间维持在 80% 以上,已经开始影响正常业务。
这是一个典型的生产环境性能问题,但第一反应并不是立刻去改 SQL,而是先搞清楚:问题到底是不是数据库引起的。
————————————————
【二、第一步:先确认是不是数据库问题】
在定位慢 SQL 之前,我先做了三件事:
1、查看流量是否异常
确认 QPS 是否突然升高,结果显示流量稳定,没有突增。
2、确认是否有代码发布
排查近期发布记录,未发现任何上线操作。
3、查看数据库资源
CPU 使用率明显升高,IO 压力同步上升。
综合以上信息,可以初步判断:问题来源于数据库侧,而不是业务逻辑或流量问题。
————————————————
【三、第二步:快速定位慢 SQL】
确认是数据库问题后,下一步不是猜,而是直接定位具体 SQL。
首先确认 MySQL 已开启慢查询日志,long_query_time 设置为 1 秒,也就是说执行时间超过 1 秒的 SQL 都会被记录。
随后查看慢查询日志,很快发现同一条 SQL 在短时间内大量出现,执行时间集中在 1.6 秒到 2.3 秒之间。
此时已经可以明确:这条 SQL 极有可能是本次性能问题的根因。
————————————————
【四、第三步:慢 SQL 原始语句】
慢查询日志中的 SQL 如下(已脱敏):
查询 orders 表,根据 user_id 和 status 进行过滤,按 create_time 倒序排序,分页返回 20 条数据。
这类 SQL 在业务系统中非常常见,但在数据量较大时,也是慢 SQL 的高发场景。
接下来要做的,是分析它为什么会慢。
————————————————
【五、第四步:使用 EXPLAIN 分析执行计划】
对该 SQL 执行 EXPLAIN 后,得到几个关键信息:
type 为 ALL
rows 约为 300 万
Extra 中出现 Using where、Using filesort
这三个信息已经非常明显地说明了问题:
这条 SQL 发生了全表扫描,并且排序没有利用索引,而是使用了文件排序。
到这里可以确定:慢不是偶发,而是结构性问题。
————————————————
【六、第五步:索引为什么没有生效】
表结构中包含 user_id、status 和 create_time 字段,且表上已经存在 user_id 和 status 的单列索引。
问题在于:
单列索引只能满足单一条件,而当前 SQL 同时存在过滤条件和排序条件;在这种情况下,MySQL 无法利用已有索引完成排序,只能选择全表扫描加 filesort。
换句话说:不是没有索引,而是索引设计无法匹配真实的查询场景。
————————————————
【七、第六步:制定优化方案】
优化目标只有三个:
减少扫描行数
避免 filesort
尽量让 SQL 命中索引
为此,采用了两个优化手段:
第一,创建联合索引,将 user_id、status 和 create_time 组合在同一个索引中,使过滤和排序都能在索引中完成。
第二,避免使用 SELECT *,只查询业务真正需要的字段,减少回表和 IO 成本。
这是一次典型的“索引设计优化”,而不是简单改写 SQL。
————————————————
【八、第七步:验证优化效果】
在创建联合索引并调整查询字段后,再次执行 EXPLAIN:
type 由 ALL 变为 range
扫描行数从 300 万下降到几十行
Extra 中不再出现 filesort
实际效果也非常明显:
接口平均响应时间从 1.8 秒下降到 0.9 秒左右,峰值响应时间控制在 1.2 秒以内,数据库 CPU 使用率显著下降。
整体性能提升超过 50%。
————————————————
【九、一次慢 SQL 的完整排查流程总结】
回顾整个过程,我现在固定遵循以下步骤:
1、报警后先判断是否为数据库问题
2、查看监控指标,确认资源异常
3、通过慢查询日志锁定 SQL
4、使用 EXPLAIN 分析执行计划
5、判断是否为索引设计问题
6、制定最小影响的优化方案
7、验证效果并进行复盘
有了这套流程,慢 SQL 不再是“靠经验猜”。
————————————————
【十、一些容易踩的坑】
实际排查过程中,有几个点非常容易走偏:
一上来就强制使用 force index
不看 rows 就盲目修改 SQL
迷信“建了索引一定会生效”
在生产环境直接做激进改动
这些做法短期可能见效,但长期来看非常危险。
————————————————
【十一、结语】
慢 SQL 本身并不可怕,可怕的是没有一套稳定、可复用的排查方法。
当你能在较短时间内完成:定位慢 SQL、分析根因、给出明确优化方案,你在团队中的技术价值会非常明显。
本文内容由人工智能辅助生成,旨在提供参考信息。作者对内容的准确性和完整性进行了审核,但不排除存在误差的可能。读者应自行判断信息的适用性,并承担因使用本文内容而产生的风险。
882

被折叠的 条评论
为什么被折叠?



