RDS for MySQL 查询突然变慢:先查慢日志、进程列表与执行计划的 6 步定位法
一、背景与问题
一个业务库平时跑得好好的,某天早上接口突然变慢:订单列表页平时 200 毫秒返回,现在动不动 3 秒以上,偶尔还会超时。应用日志里没有报错,服务器 CPU 和内存也都不高,问题大概率出在数据库的某几条 SQL 上。
这时候最忌讳两种做法:一是凭感觉重启实例,二是直接给表加索引。前者治标不治本,后者加错索引反而拖慢写入。正确的做法是按顺序定位:先确认是不是数据库慢,再找到具体的慢 SQL,最后看执行计划决定怎么改。下面 6 步就是这套流程,每一步都有可执行的命令和核对点。
二、前提
- 已有华为云 RDS for MySQL 实例,版本为 MySQL 5.7 或 8.0,本文命令在两个版本都可用。
- 有一个可连接实例的账号,至少能执行
SHOW PROCESSLIST和查询information_schema;修改慢日志参数需要较高权限账号。 - 提前准备好出问题的大致时间段,例如“今天 10:00 之后变慢”,慢日志按时间过滤时用得上。
- 不要在业务高峰直接对大表执行会锁表的 DDL,所有修复操作都先低峰或在测试环境验证。
连接实例:
mysql -h <实例连接地址> -P 3306 -u <用户名> -p
三、分步操作
第 1 步:先确认慢在数据库,还是慢在应用
先看实例的整体运行状态,确认数据库当前是不是真的在忙:
SHOW STATUS LIKE 'Threads_running';
SHOW STATUS LIKE 'Threads_connected';
Threads_running 是正在执行的线程数。如果它长期大于 CPU 核数的 2 倍左右,说明数据库里确实有大量 SQL 在排队;如果它只有个位数,而应用端依然超时,就要先排查应用到数据库之间的网络与连接池,而不是在数据库里找原因。
第 2 步:开慢日志,定位最慢的 SQL
确认慢日志已开启,并把阈值设为 1 秒:
SHOW VARIABLES LIKE 'slow_query%';
SET GLOBAL slow_query_log = 'ON';
SET GLOBAL long_query_time = 1;
在华为云控制台也可以做同样的事:进入“云数据库 RDS”→ 找到实例 →“慢日志”,里面能直接看到按执行时间排序的慢 SQL 和出现次数,不用自己进服务器翻日志文件。先看出现次数多、平均耗时长的 SQL,单条只出现一次的先放一放。
一个常见的例子:订单列表查询
SELECT id, user_id, amount
FROM orders
WHERE status = 'PAID'
AND created_at >= '2026-11-01'
ORDER BY created_at DESC
LIMIT 20;
这类 SQL 在数据量小的时候很快,表长到百万行后,没有合适的联合索引就会变成全表扫描加排序。
第 3 步:看当前正在跑的进程,抓现行
慢日志告诉你“过去谁慢”,进程列表告诉你“现在谁在慢”:
SHOW FULL PROCESSLIST;
重点看 Time 列很大的行。再用下面这句只列出跑了 60 秒以上的会话:
SELECT id, user, host, db, time, state, info
FROM information_schema.processlist
WHERE command != 'Sleep' AND time > 60
ORDER BY time DESC;
如果看到大量会话都卡在 Sending data 状态,并且是同一条 SQL,基本可以确定这条 SQL 就是瓶颈;如果大量会话卡在 Waiting for table metadata lock,则要先处理持锁的 DDL 会话,而不是优化查询本身。
第 4 步:用 EXPLAIN 看执行计划
把第 2、3 步锁定的 SQL 拿来做执行计划分析:
EXPLAIN SELECT id, user_id, amount
FROM orders
WHERE status = 'PAID'
AND created_at >= '2026-11-01'
ORDER BY created_at DESC
LIMIT 20;
看三个字段就够了:
type:ALL表示全表扫描,是最需要修的信号;ref或range表示走了索引,一般可以接受。key:实际用到的索引名,为空表示没用上任何索引。rows:MySQL 预估要扫描的行数,越大越慢。
如果 Extra 里出现 Using filesort,说明排序也没有走索引,ORDER BY 也要一起考虑进去。
第 5 步:修索引或改写 SQL
针对上面订单查询的例子,正确的做法是建一个覆盖查询条件和排序字段的联合索引,顺序是“等值条件在前,范围条件和排序在后”:
ALTER TABLE orders ADD INDEX idx_status_created (status, created_at);
注意三点:第一,大表加索引要选业务低峰,RDS 支持在线加索引,但期间仍会有额外 IO;第二,加完索引后重新执行第 4 步的 EXPLAIN,确认 type 从 ALL 变成 range 或 ref,key 显示为新建的索引;第三,如果 SQL 里用了 SELECT *,只取需要的列,SELECT * 会让优化器更难用覆盖索引。
如果执行计划显示已经走了索引但依然慢,再看是不是写了导致索引失效的写法,例如对索引列做函数运算 DATE_FORMAT(created_at, '%Y-%m-%d') = '2026-11-01',把函数去掉,改成范围条件:
WHERE created_at >= '2026-11-01' AND created_at < '2026-11-02'
第 6 步:处理卡住的会话
如果有个别会话已经跑了很久并且阻塞了别人,确认它的 info 不是核心业务的写入后,可以结束它:
KILL <会话 id>;
会话 id 就是 SHOW PROCESSLIST 里 Id 列的数字。结束前务必确认两点:这个会话的 SQL 是不是可以重跑的查询,而不是正在提交的事务;是不是属于当前业务系统,而不是其他系统的长连接。拿不准就不要 KILL,先截图留存再处理。
四、验证
修完之后按这三项逐一核对,缺一不可:
- 同一条 SQL 重新执行
EXPLAIN,type不再是ALL,key为新建索引,rows明显下降。 - 在控制台慢日志里按同一时间段复查,该 SQL 的平均耗时下降到 1 秒以内,且不再出现在慢日志前列。
- 应用侧核对:订单列表接口的耗时恢复到平时水平,
Threads_running回落到个位数。
如果第 1 项通过但第 3 项没通过,说明瓶颈不止这一条 SQL,回到第 2 步继续看慢日志里的下一条;如果第 3 项通过但慢日志里还有其他 SQL 很慢,把它们记下来排期处理,不要一次性改多个索引。
五、常见坑
- 只加单列索引:只在
status上加索引,created_at的范围和排序还是要扫大量行,效果有限。联合索引的列顺序要和查询条件一致。 long_query_time设为 0:这样所有 SQL 都会进慢日志,磁盘很快被写满,正常建议设为 1 秒。- 高峰做 ANALYZE 或加索引:大表操作会产生额外 IO 和锁,务必选低峰,操作前先确认实例磁盘余量。
- 把
EXPLAIN的rows当真实行数:它是优化器的预估值,只能用来对比优化前后的相对变化,不能当精确值。 - KILL 错会话:结束正在提交的事务会触发回滚,回滚本身也可能很耗时,KILL 前先确认会话归属。
- 慢日志开了就不管:慢日志文件会持续增长,定期在控制台清理或设置保留期,避免磁盘满导致实例只读。
六、小结清单
- [ ] 先看
Threads_running,确认慢在数据库再动手。 - [ ] 开慢日志(阈值 1 秒),按出现次数和平均耗时锁定目标 SQL。
- [ ] 用
SHOW FULL PROCESSLIST抓当前正在跑的慢会话。 - [ ] 用
EXPLAIN看type、key、rows三个字段。 - [ ] 建联合索引(等值在前、范围和排序在后),低峰操作。
- [ ] 修复后三项验证:执行计划、慢日志、应用接口耗时。
- [ ] 常见坑逐项核对:不单列索引、不设阈值为 0、不在高峰改表、不错 KILL 会话。
按这个顺序走,大多数“突然变慢”的问题都能在半小时内定位到具体的 SQL 和索引上,剩下的再按慢日志里的下一条继续处理即可。
- 点赞
- 收藏
- 关注作者
评论(0)