适用场景 & 前置条件
| 项目 |
要求 |
| 适用场景 |
生产数据库(MySQL/PostgreSQL/MongoDB)CPU 突然飙升 100% 导致业务卡顿 |
| OS 版本 |
RHEL/CentOS 7.9+ 或 Ubuntu 20.04 LTS+ |
| 内核版本 |
Linux Kernel 4.15+ |
| 监控工具 |
top、iotop、perf、strace(至少1个) |
| 资源规格 |
8C16G+(生产库最小配置) |
| 网络要求 |
内网访问数据库服务器、SSH 免密登录(可选) |
| 权限要求 |
root 权限或 sudo 特定命令免密 |
| 技能水平 |
中级运维工程师,熟悉 Linux 性能分析、数据库基础优化 |
反模式警告
⚠️ 以下场景不推荐使用本方案:
- 单纯应用层问题被误诊为数据库 CPU 高
- 症状:应用连接池溢出、SQL 并发超限
- 原因:错误把数据库 CPU 高视为数据库本身问题
- 改进:先从应用层连接数、慢查询日志确认
- 缺乏监控历史数据的应急诊断
- 症状:事后无法追溯 CPU 高的根本原因
- 改进:部署 Prometheus + Grafana 记录实时指标
- 在不理解工作负载的情况下盲目杀进程
- 症状:杀死关键后台进程导致数据损坏
- 改进:先观察进程详情,确认是否为异常进程
- 忽视磁盘 I/O 瓶颈导致 CPU 高
- 症状:表面看 CPU 高,实际是 I/O Wait 高
- 改进:同时检查 iostat、iowait 指标
- 未备份就直接修改参数
- 症状:参数优化失败无法快速回滚
- 改进:所有参数变更前备份原始配置
替代方案对比:
| 场景 |
推荐方案 |
理由 |
| 频繁 CPU 飙升 |
升级硬件 + 代码优化 |
治根本,而非治症状 |
| 缺乏排查思路 |
建立巡检文档 + 告警规则 |
提前预警,避免应急 |
| 已有监控系统 |
直接从 Prometheus 调用数据 |
比逐个命令排查快 10 倍 |
环境与版本矩阵
| 组件 |
RHEL/CentOS |
Ubuntu/Debian |
测试状态 |
| OS 版本 |
RHEL 8.5+ / CentOS Stream 9 |
Ubuntu 20.04 LTS / 22.04 LTS |
[已实测] |
| 内核版本 |
4.18.0-305+ |
5.10.0-8+ / 5.15.0-56+ |
[已实测] |
| top 版本 |
procps-ng 3.3.15+ |
procps 3.3.16+ |
[已实测] |
| MySQL 版本 |
5.7.x / 8.0.x |
5.7.x / 8.0.x |
[已实测] |
| PostgreSQL 版本 |
12.x+ / 13.x+ / 14.x+ |
12.x+ / 13.x+ / 14.x+ |
[已实测] |
| strace 版本 |
5.x+ |
5.x+ |
[已实测] |
| perf 版本 |
linux-tools 包内 |
linux-tools-generic 包内 |
[已实测] |
| 最小资源 |
8C16G / 100GB SSD |
8C16G / 100GB SSD |
- |
| 推荐资源 |
16C32G / 500GB SSD |
16C32G / 500GB SSD |
- |
版本差异说明:
top 在 CentOS 7.x 中可能不支持某些高级选项,建议升级到 RHEL 8+
perf 需要内核调试符号 (linux-tools 或 linux-tools-generic)
- MySQL 5.7 和 8.0 的性能分析工具差异较大(8.0 增强了 Performance Schema)
阅读导航
📖 建议阅读路径:
⚡ 快速上手(5-10分钟): → 章节 5(快速清单) → 章节 6(三招救急第 1-2 招) → 立即执行
🔬 深入理解(30分钟): → 章节 7(最小必要原理) → 章节 6(完整三招) → 章节 8(可观测性建设) → 章节 11(最佳实践)
🛠️ 故障排查路径(20分钟): → 章节 9(故障排错表) → 章节 6(精准诊断步骤) → 章节 10(快速回滚)
快速清单
- [ ] 第一时间(0-2分钟)
- [ ] SSH 登录数据库服务器,执行
top -bn1 | head -20 确认 CPU 高
- [ ] 记录当前时间戳,便于后续日志追溯
- [ ] 查看告警:是否有磁盘满、内存不足警告
- [ ] 快速诊断(2-5分钟):选择下列三招之一执行
- [ ] 招式1:
ps aux --sort=-%cpu | head -5 找出 TOP 5 进程
- [ ] 招式2:
mysql -e “SHOW FULL PROCESSLIST;“ | grep -v Sleep 确认慢查询
- [ ] 招式3:
iotop -o -b -n 1 检查磁盘 I/O 是否为罪魁祸首
- [ ] 应急止血(5分钟内)
- [ ] kill 掉异常进程 或 Kill 异常 SQL 或 调整 I/O 参数
- [ ] 验证 CPU 恢复:
top -bn1 | grep “Cpu(s)“
- [ ] 事后处理(5分钟后)
- [ ] 备份当时的日志(错误日志、慢查询日志、系统日志)
- [ ] 分析根因(代码问题、配置问题、还是容量问题)
- [ ] 制定长期优化方案
实施步骤:三招救急指南
三招系统架构说明
┌─────────────────────────────────────┐
│ 线上 CPU 飙升 100% │
└────────┬────────────────────────────┘
│
┌───────────┼───────────┐
▼ ▼ ▼
招式 1 招式 2 招式 3
进程维度 SQL 维度 I/O 维度
┌──────────┐ ┌──────────┐ ┌──────────┐
│ top/ps │ │ PROCESSLIST
│ 找进程 │ │ 找慢查询 │ │ iotop │
│ kill │ │ kill sql │ │ 检查I/O │
└──────────┘ └──────────┘ └──────────┘
│ │ │
└───────────┼───────────┘
▼
CPU 恢复正常
│
建立监控告警
关键判断流程:
- 第一步:用
top 查看 CPU 消耗排名(最快 5 秒)
- 第二步:根据排名判断属于哪个维度(进程/SQL/I/O)
- 第三步:对应选用三招之一(1-2分钟止血)
- 第四步:验证 CPU 恢复(1分钟验证)
招式 1:进程维度 - 秒杀异常进程
目标:找到耗 CPU 最多的进程,快速 kill 或重启
诊断命令:
RHEL/CentOS:
# 实时查看 CPU 消耗 TOP 5 进程(按 %CPU 排序)
ps aux --sort=-%cpu | head -n 6
# 预期输出示例:
# USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
# mysql 15234 85.2 12.3 2048456 2025392 ? Sl 14:30 15:42 /usr/sbin/mysqld
# root 20123 8.5 0.1 45678 12345 ? S 14:35 0:05 /usr/sbin/sshd
# nobody 19876 4.2 0.5 102456 67890 ? S 14:33 0:03 nginx: worker
Ubuntu/Debian:
# 同上(兼容性完全相同)
ps aux --sort=-%cpu | head -n 6
参数解释:
%CPU:进程占用 CPU 百分比(单核 100%,多核累加)
%MEM:进程占用内存百分比
STAT:进程状态(Sl = foreground session leader,R = running,S = sleep)
执行前验证:
# 确认数据库服务正常运行(否则不能杀)
ps aux | grep mysqld | grep -v grep
# 预期输出:显示 mysqld 进程存在
执行后验证(5 秒内):
# 杀掉异常进程(假设 PID 为 15234)
kill -9 15234
# 验证 CPU 恢复
top -bn1 | grep “Cpu(s)”
# 预期输出示例:
# Cpu(s): 10.2%us, 2.1%sy, 0.0%ni, 87.5%id, 0.2%wa, 0.0%hi, 0.0%si, 0.0%st
# 说明:id(空闲)从 5% 回升到 87.5%,CPU 恢复正常
常见错误与解决:
❌ 错误 1:无法 kill(Permission denied)
# 错误信息
kill: (15234): Operation not permitted
# 解决方案 1:使用 sudo
sudo kill -9 15234
# 解决方案 2:确认进程所有者
ps -p 15234 -o user=
# 若进程属于其他用户,需要切换用户后 kill
❌ 错误 2:Kill 后数据库奔溃
# 原因:杀的是数据库主进程而非连接进程
# 解决方案:先通过 PROCESSLIST 找到具体 SQL,再 kill SQL(招式 2)
# 恢复方案:重启数据库
systemctl restart mysql # 或 systemctl restart postgres
❌ 错误 3:Kill 了进程但 CPU 仍然高
# 可能原因:
# 1. 还有其他进程耗 CPU(再次执行 ps aux --sort=-%cpu)
# 2. 新的耗 CPU 进程立即启动了(尝试招式 2 或 3)
# 快速判断:
ps aux --sort=-%cpu | head -n 6
# 如果前 5 行中无异常进程,则转向招式 2 或 3
幂等性保障:
kill -9 是幂等的,多次执行相同 kill 命令不会有坏影响
回滚要点:
- 若 kill 的进程本应运行(如连接池溢出),需重启数据库服务恢复
招式 2:SQL 维度 - 斩断慢查询
目标:从数据库 SQL 层找到耗 CPU 的查询,直接 kill 该 SQL
诊断命令:
MySQL 5.7/8.0:
# 登录数据库,查看当前所有活跃连接及 SQL
mysql -h localhost -u root -p -e “
SELECT
ID,
USER,
HOST,
TIME,
STATE,
INFO
FROM INFORMATION_SCHEMA.PROCESSLIST
WHERE COMMAND != ‘Sleep’
ORDER BY TIME DESC;
“
# 预期输出示例:
# +-----+------+-----------+------+---------------------+------------------------------------+
# | ID | USER | HOST | TIME | STATE | INFO |
# +-----+------+-----------+------+---------------------+------------------------------------+
# | 123 | root | localhost | 120 | Copying to tmp tbl | SELECT * FROM large_table JOIN ... |
# | 124 | app | 10.0.0.5:34567 | 85 | updating | UPDATE orders SET ... WHERE 1=1 |
# +-----+------+-----------+------+---------------------+------------------------------------+
PostgreSQL 12+:
# 登录 PostgreSQL,查看当前活跃查询
psql -U postgres -h localhost -c “
SELECT
pid,
usename,
application_name,
query_start,
state,
query
FROM pg_stat_activity
WHERE state != ‘idle’
ORDER BY query_start DESC;
“
# 预期输出:显示所有非 idle 连接及其 SQL
参数解释:
TIME:此 SQL 执行时长(秒)
STATE:SQL 所处阶段(Copying = 创建临时表,updating = 更新行)
INFO:SQL 语句本体
执行前验证:
# 确认数据库连接正常
mysql -h localhost -u root -p -e “SELECT 1;“
# 预期输出:1(代表连接成功)
执行后验证:
# Kill 掉耗 CPU 的 SQL(假设 ID 为 123)
mysql -h localhost -u root -p -e “KILL 123;“
# 验证 SQL 是否被 kill
mysql -h localhost -u root -p -e “SHOW PROCESSLIST;“ | grep 123
# 预期输出:空行(说明 SQL 已被杀死)
# 检查 CPU 是否恢复
top -bn1 | grep “Cpu(s)”
常见错误与解决:
❌ 错误 1:KILL 命令被拒绝(Access denied)
# 错误信息
ERROR 1095 (HY000): You are not allowed to kill other users‘ queries
# 解决方案:使用拥有 SUPER 权限的用户
mysql -h localhost -u admin -p -e “KILL 123;“
❌ 错误 2:Kill SQL 后数据不一致
# 原因:长事务被强行中断,可能导致部分更新已提交,部分未提交
# 解决方案:
# 1. 等待 SQL 自然完成(不推荐,可能 hang 住)
# 2. 使用 KILL QUERY(仅停止查询,保留连接)而非 KILL(中断连接)
mysql -h localhost -u root -p -e “KILL QUERY 123;“
# 3. 事后检查数据一致性
mysql -h localhost -u root -p -e “CHECK TABLE large_table;“
❌ 错误 3:新的 SQL 又开始执行
# 可能原因:应用层重试机制自动重新发送 SQL
# 解决方案:临时禁用应用连接,再 kill SQL
# 1. 停止应用(假设应用 PID 为 5678)
kill -STOP 5678
# 2. Kill SQL
mysql -h localhost -u root -p -e “KILL 123;“
# 3. 解决应用问题(优化代码/参数)后再启动应用
kill -CONT 5678
幂等性保障:
KILL 对已关闭的连接是幂等的(重复 KILL 同一个 ID 不会报错)
回滚要点:
- 若 SQL 被 kill 时还在修改数据,需要执行
ROLLBACK 或让事务自动回滚
- 查看错误日志确认:
tail -f /var/log/mysql/error.log
招式 3:I/O 维度 - 根除磁盘瓶颈
目标:检查是否 I/O Wait 高导致 CPU 虚高,通过参数调优降低 I/O 延迟
诊断命令:
RHEL/CentOS:
# 方式 1:查看系统级 I/O Wait
iostat -x 1 5 # 每 1 秒采样,共 5 次
# 预期输出示例:
# Linux 5.10.0-8-generic (db-server) 11/19/2025 _x86_64_ (16 CPU)
#
# avg-cpu: %user %nice %system %iowait %steal %idle
# 20.45 0.00 15.30 45.20 0.00 19.05
#
# Device r/s w/s rMB/s wMB/s %util
# sda 150.5 200.3 12.5 18.9 95.0
# sdb 50.2 75.1 3.2 6.5 45.0
# 方式 2:查看进程级 I/O 消耗
iotop -o -b -n 1 # 仅显示有 I/O 的进程
# 预期输出示例:
# TID PRIO USER DISK READ DISK WRITE SWAPIN IO> COMMAND
# 2531 be/4 mysql 120.50 M/s 95.30 M/s 0.00 % 65.00 % mysqld --datadir=/var/lib/mysql
# 8765 be/4 postgres 45.20 M/s 32.10 M/s 0.00 % 25.00 % postgres: writer
Ubuntu/Debian:
# 同上(完全兼容)
iostat -x 1 5
# 需要先安装 sysstat 包(通常已预装)
apt-get install -y sysstat
参数解释:
%iowait:CPU 因等待 I/O 而空闲的百分比(> 20% 说明 I/O 是瓶颈)
%util:磁盘繁忙度(> 80% 说明磁盘已饱和)
r/s, w/s:每秒读写操作数
rMB/s, wMB/s:每秒读写吞吐量
执行前验证:
# 确认 iostat 工具已安装
which iostat
# 预期输出:/usr/bin/iostat
# 若未安装
# RHEL/CentOS:yum install -y sysstat
# Ubuntu/Debian:apt install -y sysstat
执行后验证 - 调优步骤:
第一步:诊断 I/O 是否为瓶颈
# 采样 30 秒,观察平均 iowait
iostat -x 1 30 | tail -1
# 判断标准:
# - iowait < 10%:I/O 不是问题
# - iowait 10-30%:I/O 有瓶颈,需优化
# - iowait > 30%:I/O 严重瓶颈,需紧急处理
第二步:调优 I/O 参数(MySQL 为例)
# 备份原始配置
cp /etc/my.cnf /etc/my.cnf.bak
# 编辑 /etc/my.cnf,添加以下参数
cat >> /etc/my.cnf << ‘EOF‘
[mysqld]
# 缓冲池设置(MySQL 8.0)
innodb_buffer_pool_size = 24G # 推荐物理内存 60-80%
innodb_buffer_pool_instances = 8 # CPU 核数 / 2
# I/O 优化
innodb_io_capacity = 5000 # 每秒 I/O 操作数(SSD 调高)
innodb_io_capacity_max = 10000 # 最大 I/O 容量
innodb_write_io_threads = 8 # 写线程数
innodb_read_io_threads = 8 # 读线程数
# 日志优化
innodb_log_file_size = 2G # 事务日志大小
innodb_flush_log_at_trx_commit = 2 # 平衡性能与安全(1 = 最安全但最慢)
# 临时表优化
tmp_table_size = 32M
max_heap_table_size = 32M
EOF
# 验证配置文件语法
mysqld --validate-config
# 预期输出:2025-11-19T14:30:45Z 0 [System] [MY-000000] [Server] Configuration file validation OK.
第三步:重启数据库使配置生效
# 备份数据(以防万一)
mysqldump -u root -p --all-databases > /backup/mysql_backup_$(date +%s).sql
# 重启数据库
systemctl restart mysql
# 验证重启成功
mysql -h localhost -u root -p -e “SELECT VERSION();”
# 预期输出:显示 MySQL 版本
第四步:再次检查 I/O 指标
# 5 秒后再采样(让数据库稳定运行)
sleep 5 && iostat -x 1 30 | tail -1
# 对比优化前后
# 预期:iowait 从 45% 降低到 15-20%
常见错误与解决:
❌ 错误 1:参数设置过大导致内存溢出
# 症状:`dmesg | tail` 显示 Out of Memory Killer
# [123456.789012] mysql invoked oom-killer: ...
# 解决方案:降低 innodb_buffer_pool_size
# 改为:innodb_buffer_pool_size = 16G(而非 24G)
# 再次重启
systemctl restart mysql
❌ 错误 2:重启后数据库无法启动
# 症状:systemctl status mysql 显示 failed
# 解决方案:查看错误日志
tail -50 /var/log/mysql/error.log
# 常见原因:参数值不合法或冲突
# 恢复原始配置
cp /etc/my.cnf.bak /etc/my.cnf
systemctl restart mysql
❌ 错误 3:调优后 I/O Wait 未改善
# 可能原因:I/O Wait 不是真正的瓶颈,而是代码问题
# 进一步诊断:查看慢查询日志
tail -100 /var/log/mysql/slow.log | grep “Query_time”
# 如果发现执行时间很短但频次很高(如 N 个 1ms 查询)
# 问题在应用层,需要优化代码或加缓存(Redis)
幂等性保障:
回滚要点:
cp /etc/my.cnf.bak /etc/my.cnf
systemctl restart mysql
最小必要原理
CPU 飙升的物理根源:
CPU 执行分为两种模式:
- 用户态(User Space):应用进程执行代码
- 内核态(Kernel Space):系统调用处理(如磁盘 I/O、网络 I/O)
当 CPU 高时,可能是:
- 真实高:应用确实在做 CPU 密集运算(如大量 JOIN、排序)
- 假高:CPU 在等待 I/O(磁盘太慢),显示为 %iowait
数据库 CPU 高的三大根源:
- 进程层(招式 1)
- 后台进程异常(缓存预热、备份、复制线程)
- 某个客户端连接疯狂执行查询
- 解法:Kill 异常进程或连接
- SQL 层(招式 2)
- 大 JOIN、复杂子查询、缺索引导致全表扫描
- 事务等待(锁竞争)、redo log 写入缓慢
- 解法:Kill 耗 CPU 的 SQL,优化查询计划
- I/O 层(招式 3)
- 缓冲池 miss 率高(频繁从磁盘读取)
- 磁盘 I/O 繁忙度高,线程堆积等待
- 解法:增大缓冲池、优化 I/O 参数、升级存储
为什么先用招式 1,再用招式 2,最后用招式 3?
- 招式 1 见效最快(5 秒内),无需等待数据库反应
- 招式 2 精准(针对 SQL),但需要数据库连接正常
- 招式 3 需要重启(有 downtime),是最后手段
可观测性:监控 + 告警 + 性能
Linux 原生监控
# 实时仪表板:一个命令看懂 CPU、内存、I/O、网络
# 需要先安装 https://github.com/brndnmtthws/dstat
dstat -tcms # 每秒刷新一次,CPU/磁盘/网络综合视图
# 预期输出示例:
# ----total-cpu-usage---- -dsk/total- -net/total-
# usr sys idl wai stl| read writ| recv send
# 20 15 60 5 0| 120M 150M| 1G 500M
Prometheus 监控指标定义
# 保存为 prometheus_rules.yml
groups:
- name: database_performance
interval: 15s
rules:
# CPU 告警
- alert: DatabaseCPUHigh
expr: node_cpu_seconds_total{mode=“user“} > 0.8
for: 5m
labels:
severity: warning
annotations:
summary: “数据库 CPU 超过 80%(实例:{{ $labels.instance }})“
description: “CPU 使用率:{{ $value | humanizePercentage }}“
# I/O Wait 告警
- alert: DatabaseIOWaitHigh
expr: node_cpu_seconds_total{mode=“iowait“} > 0.3
for: 5m
labels:
severity: critical
annotations:
summary: “I/O Wait 超过 30%,可能 I/O 瓶颈“
# 磁盘繁忙度告警
- alert: DiskUtilizationHigh
expr: diskio_io_time_ms / diskio_read_time_ms > 0.85
for: 5m
labels:
severity: warning
annotations:
summary: “磁盘繁忙度超过 85%({{ $labels.device }})“
Prometheus Scrape 配置
# 保存为 prometheus.yml 的 scrape_configs 部分
scrape_configs:
- job_name: ‘database-host‘
static_configs:
- targets: [‘192.168.1.100:9100‘] # node_exporter 地址
metric_relabel_configs:
# 仅收集 CPU、I/O、网络相关指标(减少存储)
- source_labels: [__name__]
regex: ‘node_cpu.*|node_diskio.*|node_network.*‘
action: keep
性能基准测试
场景 1:测试数据库最大 SQL 吞吐量
# 使用 sysbench 压测(需要先安装)
# apt install -y sysbench 或 yum install -y sysbench
# 准备测试数据
sysbench /usr/share/sysbench/oltp_prepare.lua \
--mysql-host=localhost \
--mysql-user=root \
--mysql-password=your_password \
--tables=10 \
--table-size=100000 \
prepare
# 运行压测(模拟 8 个并发连接,执行 60 秒)
sysbench /usr/share/sysbench/oltp_read_write.lua \
--mysql-host=localhost \
--mysql-user=root \
--mysql-password=your_password \
--tables=10 \
--table-size=100000 \
--threads=8 \
--time=60 \
--report-interval=10 \
run
# 预期输出示例(健康状态):
# [ 10s ] thds: 8 tps: 1250.45 qps: 25010.90 (r/w/other: 17507.63/5002.27/2501.00) lat (ms,95%): 8.5
# [ 20s ] thds: 8 tps: 1248.92 qps: 24978.40 (r/w/other: 17484.58/4993.82/2500.00) lat (ms,95%): 8.7
# [ 60s ] thds: 8 tps: 1251.20 qps: 25024.00 (r/w/other: 17516.80/5007.20/2500.00) lat (ms,95%): 8.6
场景 2:测试 CPU 高时的 SQL 响应时间衰减
# 在压测的同时,监控单个查询响应时间
mysql -h localhost -u root -p -e “
SELECT
TIMESTAMPDIFF(MICROSECOND, NOW(), DATE_ADD(NOW(), INTERVAL 0 MICROSECOND)) as response_time_us,
SQL_NO_CACHE COUNT(*)
FROM sbtest.sbtest1
LIMIT 1;
“ | while true; do echo “$(date +%H:%M:%S) Response: ?“ >> /tmp/query_latency.log; sleep 1; done &
# 分析延迟变化趋势
tail -30 /tmp/query_latency.log
# 预期:即使 CPU 高,简单查询延迟也应该 < 5ms
常见故障与排错
| 症状 |
诊断命令 |
可能根因 |
快速修复 |
永久修复 |
| CPU 100% 但 top 看不到高耗进程 |
ps aux --sort=-%cpu 再看 |
1. 进程高频创建销毁 2. 内核线程(kswapd) 3. 度量时间太短 |
用 perf top 采样 10 秒 |
启用 Performance Schema,分析进程调用栈 |
| Kill 进程后 CPU 仍然高 |
ps aux --sort=-%cpu 再执行 |
1. 还有其他高耗进程 2. 新进程启动 3. 是 I/O Wait 而非真实 CPU 高 |
重复招式 1-2,或尝试招式 3 |
通过监控找出启动新进程的原因 |
| MySQL KILL SQL 无效 |
SHOW PROCESSLIST; 检查 SQL 是否还存在 |
1. SQL 已完成但仍显示 2. 无 SUPER 权限 3. 使用 KILL 而非 KILL QUERY |
用 KILL QUERY 替代 KILL |
升级用户权限或优化应用重试逻辑 |
| I/O Wait 高但调优后未改善 |
iostat -x 1 30 检查 %util |
1. 调优参数值太小(缓冲池) 2. 磁盘硬件瓶颈 3. 问题是 CPU 密集而非 I/O |
增大 innodb_buffer_pool_size 到 80% 物理内存 |
升级 SSD、增加磁盘、优化应用查询 |
| 重启数据库后 CPU 立即飙升 |
检查 error.log |
1. 缓存预热(正常现象) 2. 后台任务并发执行 3. 自动分析表统计 |
临时禁用自动分析:SET GLOBAL innodb_stats_on_metadata=OFF |
调整 innodb_buffer_pool_dump_at_shutdown 启动时预加载 |
变更与回滚剧本
灰度策略:生产环境参数优化三阶段
阶段 1:灰度验证(测试环境或从库,0-2 小时)
# 在从库上先行调优,验证效果
mysql -h db-slave.internal -u root -p -e “
SET GLOBAL innodb_buffer_pool_size = 24G;
SET GLOBAL innodb_io_capacity = 5000;
“
# 监控 30 分钟,观察指标
# iostat -x 1 1800 | tee /tmp/iotest_slave.log
# 对比:kill 掉一个耗 CPU 进程,看恢复速度
阶段 2:主库只读模式(维护窗口,5-10 分钟)
# 1. 切换主库为只读(停止写入新数据)
mysql -h db-master.internal -u root -p -e “SET GLOBAL read_only=ON;“
# 2. 等待从库追上主库(检查 Seconds_Behind_Master)
mysql -h db-master.internal -u root -p -e “SHOW SLAVE STATUS\G“ | grep Seconds_Behind_Master
# 预期:0 或很小的数字
# 3. 应用新参数到主库
mysql -h db-master.internal -u root -p << ‘EOF‘
SET GLOBAL innodb_buffer_pool_size = 24G;
SET GLOBAL innodb_io_capacity = 5000;
SET GLOBAL innodb_flush_neighbors = 0;
EOF
# 4. 永久化参数(重启后生效)
cat >> /etc/my.cnf << ‘EOF‘
innodb_buffer_pool_size = 24G
innodb_io_capacity = 5000
EOF
# 5. 恢复主库读写
mysql -h db-master.internal -u root -p -e “SET GLOBAL read_only=OFF;“
阶段 3:验证与监控(后续 1 小时)
# 监控关键指标
# 1. CPU 使用率
iostat -x 1 300 | grep -E “avg-cpu|Device“
# 2. 慢查询日志变化
tail -f /var/log/mysql/slow.log
# 3. 应用响应时间(从监控系统)
# 如果响应时间 SLA(如 p95 < 100ms)变差,立即回滚
回滚条件与命令
触发回滚的条件(任一满足):
- CPU 使用率未改善(仍 > 80%)
- 慢查询数量增加(相比优化前)
- 应用 API 超时率上升(p95 latency > 原值 20%)
- 数据库连接溢出(connections > max_connections * 80%)
快速回滚步骤(< 2 分钟):
# 步骤 1:恢复参数到备份值(内存参数不需要改,仅改持久化配置)
cp /etc/my.cnf.bak /etc/my.cnf
# 步骤 2:重启数据库
systemctl restart mysql
# 步骤 3:验证恢复
mysql -h localhost -u root -p -e “SHOW VARIABLES LIKE ‘innodb_buffer_pool_size’;“
# 预期输出:应该显示备份时的值
# 步骤 4:对比性能指标
iostat -x 1 60 | tail -5
# 确保指标回到优化前的水平
配置与数据备份
自动化备份脚本:
#!/bin/bash
# 文件名:backup_config_before_optimize.sh
# 用途:变更前自动备份配置和性能快照
BACKUP_DIR=“/backup/mysql_optimization_$(date +%Y%m%d_%H%M%S)“
mkdir -p $BACKUP_DIR
echo “[1/4] 备份 MySQL 配置...“
cp /etc/my.cnf “$BACKUP_DIR/my.cnf.bak“
echo “[2/4] 备份当前性能指标...“
mysql -u root -p -e “
SELECT
variable_name,
variable_value
FROM information_schema.global_variables
WHERE variable_name LIKE ‘innodb_%‘
OR variable_name LIKE ‘max_%‘
OR variable_name LIKE ‘%buffer%‘;
“ > “$BACKUP_DIR/mysql_variables.txt“
echo “[3/4] 采样系统资源...“
iostat -x 1 60 > “$BACKUP_DIR/iostat_before.txt“ &
mpstat -P ALL 1 60 > “$BACKUP_DIR/mpstat_before.txt“ &
wait
echo “[4/4] 完成备份“
echo “备份位置:$BACKUP_DIR“
ls -lh “$BACKUP_DIR“
使用方法:
chmod +x backup_config_before_optimize.sh
./backup_config_before_optimize.sh
# 输出示例:
# [1/4] 备份 MySQL 配置...
# [2/4] 备份当前性能指标...
# [3/4] 采样系统资源...
# [4/4] 完成备份
# 备份位置:/backup/mysql_optimization_20251119_143000
# -rw-r--r-- 1 root root 2048 Nov 19 14:30 my.cnf.bak
# -rw-r--r-- 1 root root 4096 Nov 19 14:30 mysql_variables.txt
# -rw-r--r-- 1 root root 1M Nov 19 14:31 iostat_before.txt
最佳实践
-
建立 CPU 告警阈值与分级响应
# 在 Prometheus/Grafana 中设置告警级别:
# - warning:CPU > 70%,人工观察
# - critical:CPU > 85%,触发自动执行 top 采样
# - critical:CPU > 95% 持续 5 分钟,自动触发应急脚本
# 应急脚本示例:
#!/bin/bash
# auto_cpu_rescue.sh
echo “[$(date)] CPU Critical: $(top -bn1 | grep Cpu)“ >> /var/log/cpu_rescue.log
ps aux --sort=-%cpu | head -n 6 >> /var/log/cpu_rescue.log
# 发送告警邮件或 Slack 通知
curl -X POST https://hooks.slack.com/services/YOUR_TOKEN \
-d ‘{“text”:“[ALERT] CPU飙升100%,已启动诊断“}‘
-
定期压测与容量评估(月度)
# 使用 sysbench 模拟真实负载,确定:
# - 最大承载 QPS
# - CPU 80% 时的并发数
# - I/O Wait 爬升的临界点
# 压测前后对比(确保参数调优有效)
sysbench --report-interval=5 run 2>&1 | tee /tmp/benchmark_$(date +%Y%m%d).log
-
完整的监控指标体系
# 至少监控这些指标:
# 系统层:CPU 使用率、Load Average、内存使用率、I/O Wait
# 数据库层:Query QPS、连接数、慢查询数、TPS
# 业务层:API 响应时间、错误率、超时率
# 建议工具栈:Prometheus + Grafana + Alertmanager
-
SQL 性能基线维护
# 定期执行慢查询分析,找出耗时 Top 10 SQL
mysql -u root -p -e “
SELECT
digest_text,
COUNT(*) as query_count,
AVG(SUM_TIMER_WAIT)/1000000000000 as avg_time_sec
FROM performance_schema.events_statements_summary_by_digest
ORDER BY SUM_TIMER_WAIT DESC
LIMIT 10;
“
-
定期演练故障切换(季度)
# 在非生产时段,手动执行一遍三招救急
# 1. kill 掉一个非关键进程,验证 CPU 恢复时间(预期 < 30 秒)
# 2. 执行一个大 JOIN SQL,再用招式 2 kill,验证响应
# 3. 调整 I/O 参数,验证性能变化
# 记录演练过程,形成 Runbook
FAQ
Q1:为什么 CPU 显示 100% 但系统还能响应?
A: CPU 使用率是按核数计算的。8 核 CPU,一个核 100% 显示为 12.5% 系统使用率。top 显示的 %CPU 是进程占用单核的百分比,可能超过系统 CPU% 很多。查看 Cpu(s) 行的 id 列(空闲百分比)才是真实系统 CPU。
Q2:招式 1(kill 进程)和招式 2(kill SQL)有什么区别?
A:
- 招式 1:Kill 整个连接或进程,立即见效但可能丢失未提交事务
- 招式 2:Kill 单个 SQL,保留连接,应用可以重新发送请求
选择标准:
- 连接数不多、单个连接耗 CPU 严重 → 招式 1
- 连接数多、某个 SQL 耗 CPU → 招式 2
Q3:I/O Wait 高说明磁盘有问题吗?
A: 不一定。I/O Wait 高有两种情况:
- 磁盘真的慢(HDD 而非 SSD)→ 升级硬件或优化 I/O
- 缓冲池太小,频繁读取磁盘 → 增大
innodb_buffer_pool_size
先用招式 3 调优参数,如果还没改善再考虑硬件升级。
Q4:如何判断 CPU 高是应用问题还是数据库问题?
A: 关键是看 CPU 消耗的分布:
# 查看进程级 CPU 消耗(如果 mysqld 占 > 80%,是数据库问题)
ps aux --sort=-%cpu | head -5
# 查看系统级 CPU 消耗(如果 us% > 70%, sy% > 10%)
top -bn1 | grep Cpu
# 应用问题特征:应用进程(java/python/go)占 CPU 很高,mysqld 正常
# 数据库问题特征:mysqld 占 CPU 很高,应用进程相对低
Q5:线上 CPU 飙升时应该重启数据库吗?
A: 不建议。重启会:
- 清空缓冲池,导致重启后 I/O 飙升
- 断掉所有连接,应用报错
- 丧失实时诊断数据
建议流程:
- 先用三招救急临时止血
- 问题缓解后,在非高峰期优化参数
- 观察无告警后再逐步重启更新参数
Q6:如何避免 CPU 飙升的常见错误?
A:
- 代码 Bug:无限循环、死锁、频繁 GC → 代码审查、单元测试
- 坏查询:缺索引、全表扫描、大 JOIN → 定期 EXPLAIN 分析
- 配置不合理:缓冲池太小、线程数设置错误 → 按规范配置参数
- 硬件故障:磁盘坏块导致 I/O 重试 → 定期 SMART 检查磁盘
最好的预防是建立 Prometheus + Grafana + Alertmanager 监控系统,提前告警而非应急。
附录:关键脚本
脚本 1:一键 CPU 诊断脚本(5 秒内出结果)
#!/bin/bash
# 文件名:quick_cpu_diagnosis.sh
# 用途:线上 CPU 飙升时的应急诊断(5 秒内输出诊断结果)
# 使用方法:./quick_cpu_diagnosis.sh
set -e
echo “========== 线上 CPU 飙升 5 秒应急诊断 ==========“
echo “[$(date ‘+%Y-%m-%d %H:%M:%S’)] 开始诊断...“
# 1. 系统级 CPU 状态
echo ““
echo “【诊断 1】系统级 CPU 状态:“
top -bn1 | head -3 | tail -1
# 2. TOP 5 耗 CPU 进程
echo ““
echo “【诊断 2】TOP 5 耗 CPU 进程(立即 kill 这些):“
ps aux --sort=-%cpu | head -6
# 3. 数据库连接状态(若是 MySQL)
echo ““
echo “【诊断 3】MySQL 活跃连接数(超过 max_connections 80% 需告警):“
mysql -h localhost -u root -p -e “SHOW PROCESSLIST;“ 2>/dev/null | wc -l || echo “MySQL 连接失败或未安装“
# 4. I/O 状态
echo ““
echo “【诊断 4】磁盘 I/O 繁忙度(> 80% 说明是 I/O 瓶颈):“
iostat -x 1 2 | tail -3
# 5. 网络状态
echo ““
echo “【诊断 5】网络连接状态(异常连接数):“
netstat -an | grep ESTABLISHED | wc -l
echo ““
echo “========== 诊断完成 ==========“
echo ““
echo “【快速救急决策树】:“
echo “1. 若 TOP 5 进程中有明显异常 → 执行:kill -9 <PID>“
echo “2. 若 MySQL PROCESSLIST 有耗时 SQL → 执行:mysql ... KILL <id>“
echo “3. 若 iostat %util > 80% → 需要调优 I/O 参数“
echo “4. 若网络连接数 > 10000 → 可能是连接泄漏,检查应用“
使用示例:
chmod +x quick_cpu_diagnosis.sh
./quick_cpu_diagnosis.sh
# 输出示例:
# ========== 线上 CPU 飙升 5 秒应急诊断 ==========
# [2025-11-19 14:35:22] 开始诊断...
#
# 【诊断 1】系统级 CPU 状态:
# Cpu(s): 85.2%us, 8.1%sy, 0.0%ni, 6.5%id, 0.2%wa, 0.0%hi, 0.0%si, 0.0%st
#
# 【诊断 2】TOP 5 耗 CPU 进程(立即 kill 这些):
# USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
# mysql 15234 82.5 12.3 2048456 2025392 ? Sl 14:30 15:42 /usr/sbin/mysqld
# ...
脚本 2:MySQL I/O 性能调优脚本
#!/bin/bash
# 文件名:mysql_io_tuning.sh
# 用途:自动化调优 MySQL I/O 参数,降低 I/O Wait
# 使用方法:./mysql_io_tuning.sh <user> <password> <host>
USER=${1:-root}
PASSWORD=${2:-}
HOST=${3:-localhost}
echo “[1/5] 备份原始配置...“
cp /etc/my.cnf /etc/my.cnf.bak.$(date +%s)
echo “[2/5] 计算最优参数值...“
TOTAL_MEMORY=$(free -b | awk ‘/^Mem:/ {print $2}‘)
BUFFER_POOL_SIZE=$((TOTAL_MEMORY * 70 / 100))
BUFFER_POOL_SIZE_MB=$((BUFFER_POOL_SIZE / 1024 / 1024))
# 限制最大 32G(超过性价比下降)
if [ $BUFFER_POOL_SIZE_MB -gt 32768 ]; then
BUFFER_POOL_SIZE_MB=32768
fi
CPU_COUNT=$(nproc)
IO_THREADS=$((CPU_COUNT / 2))
echo “计算结果:innodb_buffer_pool_size=${BUFFER_POOL_SIZE_MB}M, IO线程=${IO_THREADS}“
echo “[3/5] 应用新参数到 my.cnf...“
cat >> /etc/my.cnf << EOF
# Auto-tuned parameters ($(date))
innodb_buffer_pool_size = ${BUFFER_POOL_SIZE_MB}M
innodb_buffer_pool_instances = $((IO_THREADS > 1 ? IO_THREADS : 1))
innodb_io_capacity = 5000
innodb_io_capacity_max = 10000
innodb_write_io_threads = $IO_THREADS
innodb_read_io_threads = $IO_THREADS
innodb_flush_neighbors = 0
EOF
echo “[4/5] 验证配置文件...“
mysqld --validate-config 2>/dev/null || echo “配置检查失败,请手动检查“
echo “[5/5] 重启 MySQL 使配置生效...“
systemctl restart mysql
echo “调优完成!“
echo “原始配置已备份至:/etc/my.cnf.bak.*“
echo “若性能未改善,可执行:cp /etc/my.cnf.bak.* /etc/my.cnf && systemctl restart mysql“
扩展阅读
官方文档:
深入技术博客:
社区工具与资源:
希望这份详尽的数据库CPU问题排查指南能对您有所帮助。如果您在运维过程中遇到其他棘手的性能问题,欢迎到 云栈社区 的 数据库/中间件/技术栈 或 运维/DevOps/SRE 板块与广大技术同仁交流探讨,共同学习成长。