MySQL数据库CPU使用率飙高是生产环境中频发的紧急故障,当监控告警显示其CPU使用率突破80%、甚至飙升至100%时,会直接导致业务响应延迟、接口超时,若未能快速定位根因并处置,最终可能引发整个服务不可用,造成严重业务损失。
面对这类紧急情况,很多运维人员容易陷入两个极端:一是将所有问题归咎于数据库本身,盲目扩容硬件资源或重启数据库,不仅无法解决根本问题,还可能因重启导致业务中断;二是将所有责任推给业务方,一味要求开发人员紧急“优化SQL”,忽视了数据库配置、并发控制等自身层面的问题。这两种做法均不够专业,也难以高效解决故障。
MySQL CPU使用率飙高的诱因具有多样性,并非单一因素导致:可能是数据库核心配置不当,未能充分适配业务场景;可能是某条慢查询长期占用CPU资源,形成性能瓶颈;可能是并发连接数超出数据库承载上限,导致资源竞争加剧;可能是锁等待异常引发CPU空转,造成资源浪费;也可能是业务流量正常增长,现有资源无法满足负载需求。不同诱因对应截然不同的处理方案,因此,作为运维工程师,快速区分、精准定位问题根源,是高效处置故障的核心前提。
本文将从系统工具监测、数据库状态排查、查询语句分析等多个维度,详细拆解MySQL CPU使用率飙高的排查思路与实操处理方法,助力运维人员快速响应、高效处置此类故障。
1 发现与确认问题
1.1 系统层面确认 CPU 使用率
首先确认 MySQL 进程确实在消耗 CPU,排除其他进程的可能性:
# 查看 MySQL 进程的 CPU 使用情况
top -bn1 | grep mysql
ps aux | grep mysql
# 查看 MySQL 的 CPU 使用率趋势
sar -u 1 10
# 查看每个 CPU 核心的使用率
mpstat -P ALL 1 5
# 查看系统负载
uptime
如果 MySQL 进程本身 CPU 使用率不高,说明问题可能在别处(比如 web 服务器、应用服务器)。如果 MySQL 进程 CPU 使用率确实很高,继续排查。
1.2 MySQL 内部状态快速检查
登录 MySQL,执行快速状态检查:
-- 查看当前连接数
SHOW STATUS LIKE 'Threads_connected';
SHOW STATUS LIKE 'Max_used_connections';
-- 查看当前正在执行的查询数
SHOW STATUS LIKE 'Threads_running';
-- 查看查询缓存命中率(MySQL 5.7 及之前)
SHOW STATUS LIKE 'Qcache%';
-- 查看 InnoDB 缓冲池状态
SHOW STATUS LIKE 'Innodb_buffer_pool%';
-- 查看锁等待情况
SHOW ENGINE INNODB STATUS\G
-- 查看事务和锁信息
SELECT * FROM information_schema.INNODB_TRX;
SELECT * FROM information_schema.INNODB_LOCK_WAITS;
Threads_running 是判断数据库是否负载过高的关键指标。如果这个值持续很高(比如超过 CPU 核心数的 2-3 倍),说明数据库处理不过来。
2 并发连接问题排查
2.1 连接数过多导致 CPU 高
高并发场景下,大量的连接建立和维护本身就会消耗 CPU:
-- 查看当前所有连接
SHOW PROCESSLIST;
SHOW FULL PROCESSLIST;
-- 按用户统计连接数
SELECT user, COUNT(*) FROM information_schema.processlist GROUP BY user;
-- 按主机统计连接数
SELECT host, COUNT(*) FROM information_schema.processlist GROUP BY host;
-- 查看最大连接数配置
SHOW VARIABLES LIKE 'max_connections';
-- 查看连接使用的内存
SHOW STATUS LIKE 'Connection_errors%';
解决方案:
-- 临时调高最大连接数
SET GLOBAL max_connections = 2000;
-- 杀掉空闲太久的连接
SELECT CONCAT('KILL ', id, ';')
FROM information_schema.processlist
WHERE Command = 'Sleep' AND Time > 3600
INTO OUTFILE '/tmp/kill_connections.sql';
SOURCE /tmp/kill_connections.sql;
-- 持久化配置(写入 my.cnf)
[mysqld]
max_connections = 2000
wait_timeout = 600
interactive_timeout = 600
2.2 连接复用不足导致频繁建立连接
应用层如果没有正确使用连接池,每次请求都新建连接,会造成大量 CPU 消耗在连接建立上:
# 查看 MySQL 的连接建立次数
SHOW STATUS LIKE 'Connections';
# 查看连接失败次数
SHOW STATUS LIKE 'Aborted%';
# 查看平均每秒新建连接数
SHOW STATUS LIKE 'Connections' LIKE '%s';
如果 Aborted_connects 很高,说明有很多连接尝试失败,可能是认证失败或权限问题。
3 慢查询导致 CPU 高
3.1 找出最消耗 CPU 的查询
-- 查看当前正在执行的所有查询
SHOW FULL PROCESSLIST;
-- 按查询时间排序,找出执行时间最长的查询
SELECT
ID,
USER,
HOST,
DB,
COMMAND,
TIME,
STATE,
LEFT(INFO, 100) AS Query
FROM information_schema.processlist
WHERE COMMAND != 'Sleep'
ORDER BY TIME DESC
LIMIT 10;
3.2 使用 Performance Schema 分析
MySQL 5.6+ 的 Performance Schema 提供了详细的查询性能数据:
-- 启用相关监控
UPDATE performance_schema.setup_instruments
SET ENABLED = 'YES'
WHERE NAME LIKE 'statement/%';
UPDATE performance_schema.setup_consumers
SET ENABLED = 'YES'
WHERE NAME LIKE 'events_statements%';
-- 查看 CPU 使用最高的查询(MySQL 8.0+)
SELECT
SCHEMA_NAME,
DIGEST,
COUNT_STAR,
SUM_TIMER_WAIT / 1000000000000 AS total_seconds,
AVG_TIMER_WAIT / 1000000000000 AS avg_seconds,
SUM_ROWS_EXAMINED,
SUM_ROWS_SENT,
SUBSTR(DIGEST_TEXT, 1, 200) AS query
FROM performance_schema.events_statements_summary_by_digest
WHERE SCHEMA_NAME = 'your_database'
ORDER BY SUM_TIMER_WAIT DESC
LIMIT 10;
3.3 常见导致 CPU 高的查询模式
全表扫描是最常见的 CPU 杀手:
-- 找出可能全表扫描的查询
EXPLAIN SELECT * FROM orders WHERE status = 'pending';
-- 如果 type 是 ALL,说明是全表扫描
-- 需要创建合适的索引
CREATE INDEX idx_orders_status ON orders(status);
排序操作消耗大量 CPU:
-- 查看哪些查询产生了 filesort
SHOW VARIABLES LIKE 'sort_buffer_size';
-- 检查查询的排序消耗
SELECT
COUNT(*) AS execution_count,
SUM_TIMER_WAIT / 1000000000000 AS total_time,
AVG_TIMER_WAIT / 1000000000000 AS avg_time,
SUBSTR(DIGEST_TEXT, 1, 100) AS query
FROM performance_schema.events_statements_summary_by_digest
WHERE DIGEST_TEXT LIKE '%ORDER BY%'
ORDER BY SUM_TIMER_WAIT DESC
LIMIT 10;
4 锁等待问题排查
4.1 锁等待导致 CPU 空转
当事务持有锁而其他事务等待时,等待的事务虽然不执行计算,但可能消耗 CPU 资源进行锁检查:
-- 查看当前锁等待情况
SELECT
r.trx_id AS waiting_trx_id,
r.trx_mysql_thread_id AS waiting_thread,
r.trx_query AS waiting_query,
b.trx_id AS blocking_trx_id,
b.trx_mysql_thread_id AS blocking_thread,
b.trx_query AS blocking_query,
b.trx_started AS blocking_started,
b.trx_rows_locked AS blocking_rows_locked
FROM information_schema.INNODB_LOCK_WAITS w
JOIN information_schema.INNODB_TRX b ON b.trx_id = w.blocking_trx_id
JOIN information_schema.INNODB_TRX r ON r.trx_id = w.requesting_trx_id;
-- 查看所有正在执行的事务
SELECT
trx_id,
trx_mysql_thread_id,
trx_state,
trx_started,
trx_rows_locked,
trx_query
FROM information_schema.INNODB_TRX;
-- 查看 InnoDB 引擎状态(包括锁信息)
SHOW ENGINE INNODB STATUS;
4.2 长时间运行的事务
未提交的长事务会持有锁,导致其他查询阻塞:
-- 找出运行时间很长的事务
SELECT
trx_id,
trx_mysql_thread_id,
trx_state,
trx_started,
TIMESTAMPDIFF(SECOND, trx_started, NOW()) AS running_seconds,
trx_rows_locked,
trx_query
FROM information_schema.INNODB_TRX
ORDER BY trx_started;
-- 找出长时间未提交的事务
SELECT
trx_mysql_thread_id,
trx_started,
TIMESTAMPDIFF(MINUTE, trx_started, NOW()) AS running_minutes,
trx_query
FROM information_schema.INNODB_TRX
WHERE TIMESTAMPDIFF(MINUTE, trx_started, NOW()) > 10;
4.3 解决锁等待问题
如果需要强制解除锁等待(谨慎操作):
-- 查看具体的连接
SHOW PROCESSLIST;
-- 杀掉持有锁的连接(需要谨慎)
KILL <thread_id>;
-- 杀掉所有长时间运行的事务(非常谨慎)
SELECT CONCAT('KILL ', trx_mysql_thread_id, ';')
FROM information_schema.INNODB_TRX
WHERE TIMESTAMPDIFF(MINUTE, trx_started, NOW()) > 30
INTO OUTFILE '/tmp/kill_long_trx.sql';
更好的做法是分析锁等待的根源,优化业务逻辑和查询语句,减少锁冲突的发生。
5 配置问题排查
5.1 缓冲区配置不当
InnoDB 缓冲池配置是最影响 CPU 性能的配置之一:
-- 查看当前缓冲池配置
SHOW VARIABLES LIKE 'innodb_buffer_pool%';
-- 查看缓冲池使用情况
SHOW STATUS LIKE 'Innodb_buffer_pool%';
-- 如果缓冲池太小,数据频繁换入换出导致 CPU 高
-- 建议设置为可用内存的 70-80%
SET GLOBAL innodb_buffer_pool_size = 17179869184; -- 16GB
5.2 并发线程配置
-- 查看线程缓存大小
SHOW VARIABLES LIKE 'thread_cache_size';
-- 查看线程创建频率
SHOW STATUS LIKE 'Threads_created';
-- 设置合理的线程缓存
SET GLOBAL thread_cache_size = 64;
-- InnoDB 脏页刷新线程数
SHOW VARIABLES LIKE 'innodb_write_io_threads';
SHOW VARIABLES LIKE 'innodb_read_io_threads';
-- 调整 I/O 线程数(SSD 场景可以提高)
SET GLOBAL innodb_write_io_threads = 16;
SET GLOBAL innodb_read_io_threads = 16;
5.3 日志配置
-- 查看日志配置
SHOW VARIABLES LIKE 'innodb_log%';
-- 日志文件太小会导致频繁的 checkpoint
-- 建议设置为 256M-1G
SHOW VARIABLES LIKE 'innodb_log_file_size';
-- 设置刷新策略(影响性能和数据安全)
SHOW VARIABLES LIKE 'innodb_flush_log_at_trx_commit';
-- 1 = 每次提交都刷新(最安全,性能最低)
-- 2 = 每次提交写操作系统缓存,由操作系统负责刷新(折中)
-- 0 = 每秒刷新一次(性能最高,可能丢失 1 秒数据)
6 业务层面分析
6.1 确认是正常业务流量还是异常
# 查看 MySQL 的查询 QPS
mysqladmin ext -i 1 | grep Questions
# 查看 MySQL 的每秒查询数
mysqladmin ext -i 1 | grep "Com_select"
# 对比历史数据,判断流量是否异常增长
如果是正常业务增长导致 CPU 飙高,需要考虑架构层面的优化(读写分离、分库分表);如果是异常流量,需要排查是否存在攻击或爬虫。
6.2 分析业务请求特征
-- 查看哪些表的查询最频繁
SELECT
OBJECT_SCHEMA,
OBJECT_NAME,
COUNT_STAR,
SUM_TIMER_WAIT / 1000000000000 AS total_seconds
FROM performance_schema.objects_summary_global_by_type
WHERE OBJECT_TYPE = 'TABLE'
ORDER BY COUNT_STAR DESC
LIMIT 20;
-- 查看哪些索引使用最频繁
SELECT
OBJECT_SCHEMA,
OBJECT_NAME,
INDEX_NAME,
COUNT_STAR,
SUM_TIMER_WAIT / 1000000000000 AS total_seconds
FROM performance_schema.table_io_waits_summary_by_index_usage
WHERE INDEX_NAME IS NOT NULL
ORDER BY COUNT_STAR DESC
LIMIT 20;
6.3 流量来源分析
# 查看当前连接的来源 IP
mysql -e "SHOW PROCESSLIST" | awk '{print $3}' | grep -v "localhost" | sort | uniq -c | sort -rn
# 查看应用层的连接池配置
# 根据连接来源判断是哪些应用在消耗资源
7 应急处理措施
7.1 短期缓解措施
如果 CPU 持续 100%,需要先缓解问题:
-- 临时关闭部分非核心功能
-- 切换到只读模式(如果有备库)
SET GLOBAL read_only = ON;
SET GLOBAL super_read_only = ON;
-- 杀掉消耗 CPU 最高的查询
SELECT CONCAT('KILL QUERY ', ID, ';')
FROM information_schema.processlist
WHERE COMMAND != 'Sleep'
ORDER BY TIME DESC
LIMIT 5;
7.2 连接限流
-- 启用连接限流(MySQL 8.0+)
SET GLOBAL max_connections = 500;
-- 设置单用户连接数限制
-- 需要在应用层实现,或者使用 MySQL Router/Proxy
7.3 查询限流
-- 临时设置慢查询阈值(避免新查询拖垮系统)
SET GLOBAL long_query_time = 0.5;
-- 如果有 pt-query-digest,可以用它设置规则拒绝特定查询
# 需要安装 percona-server 或者使用 MySQL Enterprise
8 根本解决方案
8.1 读写分离
将读操作分流到从库,减轻主库 CPU 压力:
-- 查看从库同步状态
SHOW SLAVE STATUS\G
-- 如果有多个从库,配置 MySQL Router 或 ProxySQL 做读写分离
8.2 分库分表
对于数据量巨大的表,考虑水平拆分:
-- 按时间分表(订单表常见做法)
-- orders_202401, orders_202402, orders_202403...
-- 或使用分布式数据库中间件
-- MySQL Router, ProxySQL, Vitess, TiDB
8.3 引入缓存
减少数据库查询次数:
-- 使用 Redis 缓存热点数据
-- 缓存用户 session
-- 缓存配置信息
-- 缓存统计聚合数据
9 预防措施
9.1 监控告警配置
# 配置 Prometheus + mysqld_exporter
# 关键指标:
# - mysql_global_status_threads_connected
# - mysql_global_status_threads_running
# - rate(mysql_global_status_questions[5m])
# - rate(mysql_global_status_slow_queries[5m])
9.2 定期审查慢查询
建立慢查询治理机制:
# 每天凌晨分析慢查询
pt-query-digest --since '24h' /var/log/mysql/slow.log > /tmp/slow_query_report.txt
9.3 容量规划
根据业务增长趋势,提前扩容:
-- 监控 CPU 和连接数趋势
-- 提前规划硬件扩容或架构升级
需要明确的是,MySQL CPU飙高并非孤立问题,需结合系统、数据库、业务三个层面综合分析。运维工程师首先要快速判定问题类型:是并发连接过多导致的资源竞争,是慢查询引发的CPU过载,还是锁等待造成的资源空转。
排查工作需遵循清晰的逻辑思路:第一步,从系统层面入手,确认MySQL进程是否为CPU高消耗的核心来源,排除其他进程干扰;第二步,深入数据库内部,排查核心状态指标,包括当前连接数、正在执行的查询任务、锁等待详情等,锁定异常环节;第三步,若判定为查询层面问题,借助EXPLAIN工具分析SQL执行计划,定位低效查询并优化;第四步,若为配置问题,检查数据库关键参数合理性,调整配置以适配业务负载;同时,不能忽视业务层面的分析,明确流量波动是正常增长还是异常冲击,进而选择合适的架构优化方案。
此外,要从根本上预防MySQL CPU飙高问题,还需建立完善的监控告警机制,实现CPU使用率、连接数、慢查询等指标的实时监测、提前预警,同时搭建规范的慢查询治理流程,定期优化低效SQL、调整数据库配置,从源头降低故障发生概率。
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END









暂无评论内容