MySQL数据库CPU使用率飙高排查与处理思路

MySQL数据库CPU使用率飙高排查与处理思路

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
喜欢就支持一下吧
点赞6 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容