记录第一次排查生成环境下,mysql CPU飚高100%占用,应用程序拖慢,记录,解决后占用降为正常。

一、现象:CPU 100%
前天收到使用QA语料构建系统同学反馈,语料系统响应缓慢,请求超时。用 top 一看,MySQL CPU 使用率持续 100%+,但进入容器执行 SHOW PROCESSLIST 却没有任何活跃查询,全是 Sleep 状态。有点莫名其妙。
top - 08:30:00 up 30 days, load average: 2.50, 2.30, 2.10 MiB Swap: 4096.0 total, 3964.7 free, 131.3 used. 6434.6 avail Mem PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 1878724 lxd 20 0 2809352 560612 34364 S 102.3 6.9 4:45.44 mysqld 1788 root 20 0 1007448 38620 8908 T 9.6 0.5 7053:32 oneav
二、Redo Log 满了
既然表面看不到活跃查询,先看 InnoDB 内部状态:
SHOW ENGINE INNODB STATUS\G
关键输出:
LOG --- Log capacity 104857600 Log capacity used 104857600 ← 100% 满! ... BUFFER POOL AND MEMORY --- Buffer pool size 8192 Buffer pool hit rate 948 / 1000 ← 仅 94.8%(正常 >99%) ... ROW OPERATIONS --- 175526.47 reads/s ← 异常高
分析:构建 MySQL 容器时没有配置 redo log 和 buffer pool 大小,全是默认值。
- Redo Log 总容量仅 96MB(48MB × 2 个文件),已写满 100%
- Buffer Pool 仅 128MB(8192 页 × 16KB),命中率仅 94.8%
- 配置严重偏小,导致 InnoDB 频繁刷脏页释放日志空间,消耗大量 CPU
查看具体配置:
SHOW VARIABLES LIKE 'innodb_log_file_size'; +----------------------+----------+ | Variable_name | Value | +----------------------+----------+ | innodb_log_file_size | 50331648 | -- 48MB +----------------------+----------+ SHOW VARIABLES LIKE 'innodb_log_files_in_group'; +---------------------------+-------+ | Variable_name | Value | +---------------------------+-------+ | innodb_log_files_in_group | 2 | +---------------------------+-------+
三、第一次优化:调大 Redo Log 和 Buffer Pool
修改配置文件并重启容器:
[mysqld] # Redo Log(核心问题) innodb_log_file_size = 256M innodb_log_files_in_group = 3 # 总容量 768MB # Buffer Pool innodb_buffer_pool_size = 2G # 其他优化 innodb_flush_log_at_trx_commit = 2 innodb_flush_method = O_DIRECT
重启后,MySQL CPU 从 100%+ 降到了 50% 左右,但依然偏高,问题没有彻底解决。
四、第二次优化:开启慢查询日志
怀疑还有隐藏的慢查询,于是开启慢查询日志:
SET GLOBAL slow_query_log = ON; SET GLOBAL long_query_time = 2; SET GLOBAL log_queries_not_using_indexes = ON; -- 验证配置 SHOW VARIABLES LIKE 'slow_query_log%'; +---------------------+----------------------------------+ | Variable_name | Value | +---------------------+----------------------------------+ | slow_query_log | ON | | slow_query_log_file | /var/lib/mysql/slow.log | | long_query_time | 2.000000 | +---------------------+----------------------------------+
慢查询日志记录
# Time: 2026-06-03T12:09:41.527275Z # User@Host: swust[swust] @ [172.18.0.3] Id: 398 # Query_time: 4.782904 Lock_time: 0.000003 Rows_sent: 1 Rows_examined: 310516 SET timestamp=1780488576; SELECT count(*) AS count_1 FROM (SELECT ... 31个字段 ... FROM datasets WHERE ...) AS anon_1; # Time: 2026-06-03T12:09:46.161278Z ... 相同 SQL,每 5 秒执行一次
分析:这是任务执行日志定时查询接口 暴露出的慢 SQL。
- SQL 写法问题:外层
COUNT(*)套了一个子查询,且子查询里SELECT * - 缺少索引:
WHERE条件涉及三个字段(user_id,current_stage,created_at),没有复合索引 - 高频执行:每 5 秒一次,每次扫描 31 万行,导致数据库疲于奔命
五、解决方案
1. 添加复合索引
USE qa_gen; CREATE INDEX idx_user_stage_time ON datasets(user_id, current_stage, created_at);
2. 优化 SQL 写法
-- 改造前(慢):先查全部字段再计数
SELECT COUNT(*) FROM (
SELECT * FROM datasets
WHERE user_id = 11
AND current_stage = 'question_generate'
AND created_at >= '2026-06-01 11:46:14'
) AS t;
-- 改造后(快):直接 COUNT
SELECT COUNT(*) FROM datasets
WHERE user_id = 11
AND current_stage = 'question_generate'
AND created_at >= '2026-06-01 11:46:14';
3. 应用层代码优化
# 不要这样做(慢)
count = session.query(func.count()).select_from(
session.query(Dataset).filter(...).subquery()
).scalar()
# 应该这样做(快)
count = session.query(func.count(Dataset.id)).filter(
Dataset.user_id == 11,
Dataset.current_stage == 'question_generate',
Dataset.created_at >= '2026-06-01 11:46:14'
).scalar()
六、优化效果对比
| 指标 | 优化前 | 优化后 | 改善 |
|---|---|---|---|
| 扫描行数 | 310,516 行 | ~2,000 行 | 减少 99% |
| 查询时间 | 5.08 秒 | 0.05 秒 | 快 100 倍 |
| MySQL CPU | 48–56% | <5% | 恢复正常 |
| 数据传输量 | 62 MB/次 | <1 KB/次 | 减少 99.9% |
索引使用验证
EXPLAIN SELECT COUNT(*) FROM datasets WHERE user_id = 11 AND current_stage = 'question_generate' AND created_at >= '2026-06-01 11:46:14'\G -- 优化前: -- type: ALL (全表扫描) -- rows: 310516 -- Extra: Using where -- 优化后: -- type: ref (索引查找) -- key: idx_user_stage_time (使用索引) -- rows: 1847 -- Extra: Using index (覆盖索引)
七、总结与反思
- MySQL 默认 Redo Log 仅 96MB、Buffer Pool 128MB,生产环境必须调优。
SHOW PROCESSLIST只能看到"此刻"的快照:慢查询执行时间很短(5 秒),如果查看时刚好落在空闲期,就会看到全是Sleep。慢查询日志才是定位问题的关键。- COUNT 不要套子查询:直接
COUNT(*)即可,避免无谓的全表扫描和大量数据传输。 - 为高频查询条件建立复合索引,扫描行数从 31 万降到 2 千,性能提升 100 倍。
- 高频轮询接口:每 5 秒一次的定时任务,加上低效 SQL,会把数据库拖垮。可以考虑降低频率或改用增量查询。
最终,MySQL CPU 稳定在 5% 以下,接口响应从降到毫秒级。












