1. MySQL CPU 飙高问题概述
MySQL 数据库作为最流行的关系型数据库之一,在实际生产环境中经常会遇到 CPU 使用率突然飙升的情况。这种情况不仅会影响数据库性能,严重时甚至会导致整个系统瘫痪。CPU 飙高问题看似简单,但背后可能隐藏着多种复杂原因,需要系统化的排查方法才能准确定位问题根源。
在实际工作中,我发现大多数 DBA 和开发人员在面对 CPU 飙高问题时,往往会陷入以下几个误区:
- 直接重启 MySQL 服务来"解决问题",这实际上只是掩盖了问题
- 盲目添加硬件资源,而没有找到真正的性能瓶颈
- 只关注 SQL 优化,忽略了系统层面的影响因素
正确的排查思路应该是从外到内、由表及里的系统性分析。本文将分享一套经过实战验证的三阶段排查方法论,帮助大家快速定位和解决 MySQL CPU 飙高问题。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 第一阶段:主机层定位
2.1 确认 CPU 负载情况
首先我们需要确认 CPU 负载是否真的过高,以及负载的类型。使用 top 命令可以快速获取这些信息:
bash复制top -c
重点关注 CPU 行的几个关键指标:
- us(user):用户空间 CPU 使用率。如果这个值持续高于 70%,通常说明是 MySQL 的 SQL 计算消耗了大量 CPU
- sy(system):内核空间 CPU 使用率。偏高可能意味着系统调用过多或锁竞争激烈
- wa(iowait):I/O 等待。如果这个值高而 us 不高,说明 CPU 是在等待磁盘 I/O,实际并不繁忙
- id(idle):CPU 空闲率。这个值越低说明 CPU 越繁忙
同时还要关注 load average 指标。一般来说,load average 值不应该超过服务器的 CPU 核心数。例如在 4 核服务器上,如果 load average 达到 8,说明系统已经严重过载。
2.2 定位 MySQL 进程
确认是 MySQL 导致 CPU 飙高后,我们需要获取 MySQL 的进程 ID:
bash复制ps -ef | grep mysqld
# 或
pidof mysqld
记录下 MySQL 的 PID,后续的分析都将基于这个 PID 进行。
2.3 分析 MySQL 线程 CPU 使用
MySQL 是多线程架构,每个连接通常对应一个线程。我们需要查看哪些线程消耗了最多的 CPU:
bash复制top -H -p [MySQL_PID] -d 1
这里可能会出现几种典型情况:
- 单线程 CPU 100%:通常意味着有一条非常耗时的 SQL 在执行
- 多个线程 CPU 都较高:可能是并发连接数过多或连接池配置不当
- 线程 CPU 不高但整体 CPU 高:可能是 MySQL 内部机制如锁竞争、后台线程等导致
2.4 区分用户态和内核态 CPU
使用 pidstat 工具可以更精确地分析 CPU 消耗类型:
bash复制pidstat -p [MySQL_PID] -u -h 1 5
重点关注:
- %usr:用户态 CPU,反映 SQL 计算消耗
- %system:内核态 CPU,反映系统调用等开销
- %CPU:总 CPU 使用率
3. 第二阶段:系统层分析
3.1 上下文切换检查
过多的上下文切换会消耗大量 CPU 资源。使用 vmstat 检查:
bash复制vmstat 1 5
重点关注 cs(上下文切换)和 in(中断)字段。正常情况下,上下文切换应该在几千次/秒,如果超过 20000 次/秒就需要警惕了。
进一步使用 pidstat 查看 MySQL 的上下文切换情况:
bash复制pidstat -w -p [MySQL_PID] 1 5
3.2 内存与 Swap 检查
内存不足会导致频繁的 Swap 交换,这会显著增加 CPU 开销:
bash复制free -m
vmstat 1 5
特别关注 si(swap in)和 so(swap out)指标。如果它们不为 0,说明系统正在使用 Swap,这对数据库性能是致命的。
解决方法包括:
- 增加物理内存
- 调整 MySQL 的 buffer pool 大小
- 临时禁用 Swap:
swapoff -a
3.3 网络连接检查
过多的网络连接也会消耗 CPU 资源:
bash复制netstat -an | grep ESTABLISHED | wc -l
netstat -an | grep TIME_WAIT | wc -l
如果 ESTABLISHED 连接过多,可能是连接池配置不当;TIME_WAIT 过多则可能是短连接频繁创建销毁导致。
优化建议:
- 使用连接池管理数据库连接
- 调整内核参数:
net.ipv4.tcp_tw_reuse = 1
3.4 磁盘 I/O 检查
虽然磁盘 I/O 不会直接消耗 CPU,但 I/O 等待会导致 CPU 利用率虚高:
bash复制iostat -x -k 1 5
关键指标:
- %util:设备利用率,接近 100% 表示磁盘饱和
- await:平均等待时间,超过 10ms 说明磁盘响应慢
3.5 NUMA 架构检查
在 NUMA 架构服务器上,不合理的 CPU-内存绑定会导致性能问题:
bash复制numactl --hardware
dmesg | grep -i numa
解决方法是在启动 MySQL 时使用 interleave 模式:
bash复制numactl --interleave=all /usr/sbin/mysqld
4. 第三阶段:MySQL 内部排查
4.1 实时会话分析
查看当前正在执行的 SQL:
sql复制SELECT ID, USER, HOST, DB, COMMAND, TIME, STATE, INFO
FROM information_schema.PROCESSLIST
WHERE COMMAND != 'Sleep'
ORDER BY TIME DESC
LIMIT 20;
重点关注 STATE 列,常见的有:
- Sending data:通常表示全表扫描
- Sorting result:排序操作
- Creating tmp table:创建临时表
- Waiting for lock:等待锁
4.2 慢查询分析
启用慢查询日志:
sql复制SET GLOBAL slow_query_log='ON';
SET GLOBAL long_query_time=0.1;
SET GLOBAL log_queries_not_using_indexes='ON';
分析慢查询日志:
bash复制mysqldumpslow -s t -t 10 slow.log
# 或使用更强大的 pt-query-digest
pt-query-digest slow.log
4.3 状态指标分析
检查关键状态变量:
sql复制SHOW STATUS LIKE 'Threads_running';
-- 正常应 <= CPU核心数
SHOW STATUS LIKE 'Created_tmp%';
-- 临时表创建次数
-- 计算 Buffer Pool 命中率
SELECT 1 - (SELECT variable_value FROM performance_schema.global_status WHERE variable_name = 'Innodb_buffer_pool_reads') /
(SELECT variable_value FROM performance_schema.global_status WHERE variable_name = 'Innodb_buffer_pool_read_requests') AS buffer_pool_hit_ratio;
-- 目标 >= 99%
4.4 锁与事务分析
查看 InnoDB 状态:
sql复制SHOW ENGINE INNODB STATUS\G
检查锁等待:
sql复制SELECT * FROM sys.innodb_lock_waits;
4.5 执行计划分析
对可疑 SQL 进行 EXPLAIN 分析:
sql复制EXPLAIN FORMAT=JSON SELECT ...;
危险信号包括:
- type: ALL(全表扫描)
- key: NULL(未使用索引)
- Extra: Using filesort, Using temporary
4.6 Performance Schema 分析
MySQL 5.7+ 的 Performance Schema 提供了更细粒度的监控:
sql复制SELECT EVENT_NAME, COUNT_STAR, SUM_TIMER_WAIT
FROM performance_schema.events_statements_summary_by_global_by_event_name
ORDER BY SUM_TIMER_WAIT DESC
LIMIT 10;
5. 常见问题与解决方案
根据多年实战经验,我总结了 MySQL CPU 飙高的几种常见场景及应对策略:
-
单条 SQL 消耗大量 CPU
- 现象:单个线程 CPU 100%
- 解决方案:通过 PROCESSLIST 找到问题 SQL,分析执行计划,添加适当索引
-
并发连接过多
- 现象:多个线程 CPU 都较高,Threads_running 值大
- 解决方案:优化连接池配置,限制最大连接数,使用读写分离
-
排序操作消耗 CPU
- 现象:Created_tmp_disk_tables 值高
- 解决方案:优化 SQL 避免排序,增大 sort_buffer_size
-
锁竞争激烈
- 现象:大量线程处于 Waiting for lock 状态
- 解决方案:拆分长事务,优化事务隔离级别,使用行锁代替表锁
-
Buffer Pool 命中率低
- 现象:Innodb_buffer_pool_reads 值高
- 解决方案:增大 innodb_buffer_pool_size
6. 实战排查流程建议
在实际工作中,我建议按照以下顺序进行排查:
- 首先通过 top 和 pidstat 确认 CPU 使用情况
- 检查系统层面的上下文切换、内存、I/O 等指标
- 查看 MySQL 的 PROCESSLIST 和线程状态
- 分析慢查询日志
- 对可疑 SQL 进行 EXPLAIN 分析
- 检查锁和事务情况
记住,每个生产环境都是独特的,排查时要结合具体情况灵活调整。最重要的是建立系统化的排查思路,而不是依赖单一指标或工具。
