查出正在吃cpu的sql应优先查询information_schema.processlist,重点关注state为sending data或copying to tmp table、time值较大、info含select/update的记录;再用explain分析可疑sql的type和rows,type=all或rows接近表总行数即全表扫描;索引需覆盖where+order by+limit组合,如(app, level, created_at);紧急时用pt-kill杀掉超时高cpu查询。

查出正在吃CPU的SQL:用SHOW PROCESSLIST和INFORMATION_SCHEMA.PROCESSLIST
MySQL CPU打满,第一反应不是调参数,而是看谁在“狂刷CPU”。SHOW PROCESSLIST能立刻看到当前活跃连接和它们执行的语句,但默认只显示前100条,且不包含执行时长、扫描行数等关键指标。
更可靠的是查系统表:SELECT * FROM INFORMATION_SCHEMA.PROCESSLIST WHERE COMMAND != 'Sleep' ORDER BY TIME DESC LIMIT 20。重点关注STATE为Sending data或Copying to tmp table、TIME值较大、INFO字段里带SELECT或UPDATE的记录——这些大概率就是元凶。
- 别只盯着
State = 'Query',很多慢查询卡在Sorting result或Creating sort index阶段,CPU早就在猛跑了 -
INFO字段可能被截断,加CONCAT('SELECT * FROM INFORMATION_SCHEMA.PROCESSLIST WHERE ID=', ID)再查一次完整SQL - 如果
PROCESSLIST里几乎全是Sleep,说明高CPU来自后台线程(如刷脏页、InnoDB purge),得切到SHOW ENGINE INNODB STATUS看
识别全表扫描:看EXPLAIN里的type和rows
高频SQL跑得慢,十有八九没走索引。直接对可疑SQL执行EXPLAIN,盯住两列:type和rows。
type = ALL是明确信号:MySQL正一行行扫完整张表;rows值如果接近表总行数(比如SELECT COUNT(*) FROM orders返回100万,EXPLAIN里rows=987654),基本坐实了全表扫描。
-
type = index看着比ALL好,但其实是按索引顺序全扫索引树,仍可能很慢,尤其当索引字段大、数据量大时 - 注意
key列是否为NULL,哪怕type是range或ref,key=NULL说明索引根本没被选中 - 联合索引要严格遵循最左前缀,
WHERE status = ? AND user_id = ?建了(user_id, status)索引就无效
给WHERE条件加索引:优先覆盖WHERE + ORDER BY + LIMIT组合
不是所有字段都值得建索引。重点锁死三类字段:出现在WHERE等值条件里的、ORDER BY排序字段、LIMIT分页的偏移依据(比如created_at)。
例如SELECT * FROM logs WHERE app = 'api' AND level = 'error' ORDER BY created_at DESC LIMIT 20,最优索引是(app, level, created_at)——把等值条件放前面,排序字段放最后,让MySQL一次定位+有序取出,避免Using filesort。
- 别单独给每个
WHERE字段建单列索引,INDEX(a), INDEX(b)对WHERE a=? AND b=?几乎没用,优化器通常只选一个 -
LIKE '%abc'无法用索引,LIKE 'abc%'可以,但LIKE '%abc%'基本只能靠全文索引或ES - 索引字段类型要和查询参数严格一致,
WHERE user_id = '123'(字符串)对INT字段索引会失效
临时禁用低效SQL:用pt-kill或KILL快速止损
线上CPU已爆,等不了改SQL或加索引。先干掉正在疯跑的查询,抢出窗口排查。
用pt-kill --busy-time 60 --kill --victims all --match-command Query --match-state "Sending data|Sorting result",它会自动杀掉运行超60秒且处于高CPU状态的查询。比手动KILL更稳,还能加--print先预览。
- 慎用
KILL QUERY(只终止语句)而非KILL CONNECTION(断连),后者可能引发应用重试风暴 - 如果
pt-kill不可用,写个简单脚本查PROCESSLIST里TIME > 30且INFO含SELECT的ID,批量KILL - 杀完别以为万事大吉——这些SQL大概率还会回来,必须同步记日志、加监控告警(比如
Performance Schema里统计每秒慢查询数)
真正难的不是加索引,是判断哪些查询值得优化。有些SQL一天只跑几次但单次扫千万行,有些每秒跑几百次但只查几十行——CPU贡献度得结合频率和单次开销一起算,光看EXPLAIN的rows会误判。











