CPU 飙高是 MySQL 运维和面试中的高频问题。很多人第一反应是“加索引”,但真实场景远没有这么简单。本文从面试实战角度出发,给出一套可落地的排查路径和解决方案。
一、先确认:真的是 MySQL 导致的吗?
别急着登进数据库。第一步是在操作系统层面确认 CPU 消耗的来源。
top -c
关注几个关键信息:
- 哪个进程占 CPU 最高:确认是
mysqld还是其他进程(比如备份脚本、爬虫、PHP-FPM)。 - us 高还是 sy 高:
us(用户态)高通常是 SQL 执行消耗;sy(内核态)高可能是锁竞争、上下文切换频繁。 - 负载与 CPU 核数关系:
load average持续超过核数,说明有大量线程在排队。
如果确认是 mysqld 占用过高,再进入数据库内部排查。
二、定位:是哪些 SQL 在消耗 CPU
2.1 查看当前正在执行的线程
SHOW PROCESSLIST;
-- 或
SELECT * FROM information_schema.PROCESSLIST
WHERE COMMAND != 'Sleep' ORDER BY TIME DESC;
重点看 State 和 Info 字段。常见的高 CPU 状态包括:
Sending data:大量数据扫描或返回Sorting result:排序操作消耗大Copying to tmp table:临时表操作Creating sort index:索引排序
2.2 开启慢查询日志
如果问题不是持续性的,而是间歇性飙高,慢查询日志是首选工具:
SET GLOBAL slow_query_log = ON;
SET GLOBAL long_query_time = 1;
SET GLOBAL log_queries_not_using_indexes = ON;
用 mysqldumpslow 或 pt-query-digest 分析:
pt-query-digest /var/log/mysql/slow.log
2.3 使用 performance_schema 精准定位
MySQL 5.7+ 推荐用 performance_schema 做实时分析:
-- 按总延迟排序,找出最耗 CPU 的 SQL 模板
SELECT DIGEST_TEXT, COUNT_STAR,
SUM_TIMER_WAIT/1000000000000 AS total_sec,
AVG_TIMER_WAIT/1000000000 AS avg_ms
FROM performance_schema.events_statements_summary_by_digest
ORDER BY SUM_TIMER_WAIT DESC LIMIT 10;
这个查询能直接告诉你:哪类 SQL 模板累计消耗时间最多,是定位问题的核心手段。
2.4 查看当前线程的 CPU 消耗
SELECT thread_id, thread_os_id
FROM performance_schema.threads
WHERE processlist_id = <连接ID>;
拿到 thread_os_id 后,在操作系统层面用 top -H -p <mysqld_pid> 找到对应线程,再用 perf top 或 gdb 进一步分析。
三、常见原因分类与解决方案
3.1 缺少索引或索引失效
表现:全表扫描,Handler_read_rnd_next 飙升。
排查:
EXPLAIN SELECT ...;
SHOW STATUS LIKE 'Handler_read%';
解决:为 WHERE、JOIN、ORDER BY 涉及的列添加合适索引。注意避免索引失效场景:隐式类型转换、函数操作列、前导模糊匹配。
3.2 锁竞争严重
表现:大量线程处于 Waiting for table metadata lock 或 Waiting for row lock,CPU 的 sy 占比高。
排查:
SELECT * FROM performance_schema.data_locks;
SELECT * FROM performance_schema.data_lock_waits;
-- MySQL 5.7 及以下
SELECT * FROM information_schema.INNODB_LOCKS;
SELECT * FROM information_schema.INNODB_LOCK_WAITS;
解决:缩短事务,避免大事务;DDL 操作尽量在低峰期执行;必要时 kill 掉阻塞源。
3.3 全表扫描 + 大结果集排序
表现:Sorting result 状态多,临时表落盘。
排查:
SHOW STATUS LIKE 'Created_tmp%';
SHOW STATUS LIKE 'Sort%';
解决:用索引覆盖排序,减少 filesort;分页查询避免大 OFFSET;必要时在应用层做排序。
3.4 高并发短连接
表现:Threads_created 快速增长,CPU 消耗在连接建立和销毁上。
排查:
SHOW STATUS LIKE 'Threads_%';
SHOW VARIABLES LIKE 'thread_cache_size';
解决:使用连接池;适当增大 thread_cache_size;排查是否有连接泄漏。
3.5 慢 SQL 批量并发
表现:某条 SQL 单次执行不慢,但 QPS 极高,累计 CPU 消耗大。
排查:events_statements_summary_by_digest 中 COUNT_STAR 高但 AVG_TIMER_WAIT 低。
解决:在应用层加缓存(Redis);合并请求;限流。
四、应急处理手段
当 CPU 已经打满、业务受影响时,先止血再根治:
-
Kill 掉问题线程:
KILL <thread_id>;注意:kill 事务会回滚,大事务回滚可能更慢,需评估。
-
临时限流:在代理层(如 ProxySQL)限制问题 SQL 的并发。
-
降级非核心业务:暂停报表、统计类查询。
-
紧急加索引:MySQL 5.6+ 支持 Online DDL,但大表加索引仍需谨慎,建议用
pt-online-schema-change或gh-ost。
五、面试回答框架
如果面试中被问到这个问题,建议按以下结构回答:
- 先分层:OS 层 → MySQL 层 → SQL 层,逐层缩小范围。
- 再分类:是 SQL 本身慢、锁竞争、连接风暴,还是硬件瓶颈。
- 给工具:
top、SHOW PROCESSLIST、慢查询日志、performance_schema。 - 给方案:索引优化、SQL 改写、架构调整(缓存/读写分离/分库分表)。
- 提应急:kill 线程、限流、降级。
六、总结
MySQL CPU 飙高的排查核心是“先定位、再分类、后解决”。不要一上来就加索引,而是通过 performance_schema 和慢查询日志找到真正的消耗源。常见原因无非是慢 SQL、锁竞争、高并发连接这几类,对应手段也比较成熟。真正体现水平的,是在应急场景下能快速止血,同时在事后能通过架构和规范避免问题复发。
未经允许不得转载:任鹏个人博客 » MySQL CPU 飙高如何定位并解决?面试必问排查思路

