找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

4478

积分

0

好友

612

主题
发表于 3 小时前 | 查看: 4| 回复: 0

适用场景 & 前置条件

项目 要求
适用场景 生产数据库(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 性能分析、数据库基础优化

反模式警告

⚠️ 以下场景不推荐使用本方案:

  1. 单纯应用层问题被误诊为数据库 CPU 高
    • 症状:应用连接池溢出、SQL 并发超限
    • 原因:错误把数据库 CPU 高视为数据库本身问题
    • 改进:先从应用层连接数、慢查询日志确认
  2. 缺乏监控历史数据的应急诊断
    • 症状:事后无法追溯 CPU 高的根本原因
    • 改进:部署 Prometheus + Grafana 记录实时指标
  3. 在不理解工作负载的情况下盲目杀进程
    • 症状:杀死关键后台进程导致数据损坏
    • 改进:先观察进程详情,确认是否为异常进程
  4. 忽视磁盘 I/O 瓶颈导致 CPU 高
    • 症状:表面看 CPU 高,实际是 I/O Wait 高
    • 改进:同时检查 iostat、iowait 指标
  5. 未备份就直接修改参数
    • 症状:参数优化失败无法快速回滚
    • 改进:所有参数变更前备份原始配置

替代方案对比:

场景 推荐方案 理由
频繁 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-toolslinux-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分钟):选择下列三招之一执行
    • [ ] 招式1ps aux --sort=-%cpu | head -5 找出 TOP 5 进程
    • [ ] 招式2mysql -e “SHOW FULL PROCESSLIST;“ | grep -v Sleep 确认慢查询
    • [ ] 招式3iotop -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 恢复正常
                             │
                      建立监控告警

关键判断流程:

  1. 第一步:用 top 查看 CPU 消耗排名(最快 5 秒)
  2. 第二步:根据排名判断属于哪个维度(进程/SQL/I/O)
  3. 第三步:对应选用三招之一(1-2分钟止血)
  4. 第四步:验证 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

参数解释:

  1. %CPU:进程占用 CPU 百分比(单核 100%,多核累加)
  2. %MEM:进程占用内存百分比
  3. 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

参数解释:

  1. TIME:此 SQL 执行时长(秒)
  2. STATE:SQL 所处阶段(Copying = 创建临时表,updating = 更新行)
  3. 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

参数解释:

  1. %iowait:CPU 因等待 I/O 而空闲的百分比(> 20% 说明 I/O 是瓶颈
  2. %util:磁盘繁忙度(> 80% 说明磁盘已饱和)
  3. r/s, w/s:每秒读写操作数
  4. 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 执行分为两种模式:

  1. 用户态(User Space):应用进程执行代码
  2. 内核态(Kernel Space):系统调用处理(如磁盘 I/O、网络 I/O)

当 CPU 高时,可能是:

  • 真实高:应用确实在做 CPU 密集运算(如大量 JOIN、排序)
  • 假高:CPU 在等待 I/O(磁盘太慢),显示为 %iowait

数据库 CPU 高的三大根源:

  1. 进程层(招式 1)
    • 后台进程异常(缓存预热、备份、复制线程)
    • 某个客户端连接疯狂执行查询
    • 解法:Kill 异常进程或连接
  2. SQL 层(招式 2)
    • 大 JOIN、复杂子查询、缺索引导致全表扫描
    • 事务等待(锁竞争)、redo log 写入缓慢
    • 解法:Kill 耗 CPU 的 SQL,优化查询计划
  3. 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)变差,立即回滚

回滚条件与命令

触发回滚的条件(任一满足):

  1. CPU 使用率未改善(仍 > 80%)
  2. 慢查询数量增加(相比优化前)
  3. 应用 API 超时率上升(p95 latency > 原值 20%)
  4. 数据库连接溢出(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

最佳实践

  1. 建立 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%,已启动诊断“}‘
  2. 定期压测与容量评估(月度)

    # 使用 sysbench 模拟真实负载,确定:
    # - 最大承载 QPS
    # - CPU 80% 时的并发数
    # - I/O Wait 爬升的临界点
    
    # 压测前后对比(确保参数调优有效)
    sysbench --report-interval=5 run 2>&1 | tee /tmp/benchmark_$(date +%Y%m%d).log
  3. 完整的监控指标体系

    # 至少监控这些指标:
    # 系统层:CPU 使用率、Load Average、内存使用率、I/O Wait
    # 数据库层:Query QPS、连接数、慢查询数、TPS
    # 业务层:API 响应时间、错误率、超时率
    
    # 建议工具栈:Prometheus + Grafana + Alertmanager
  4. 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;
    “
  5. 定期演练故障切换(季度)

    # 在非生产时段,手动执行一遍三招救急
    # 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 高有两种情况:

  1. 磁盘真的慢(HDD 而非 SSD)→ 升级硬件或优化 I/O
  2. 缓冲池太小,频繁读取磁盘 → 增大 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: 不建议。重启会:

  1. 清空缓冲池,导致重启后 I/O 飙升
  2. 断掉所有连接,应用报错
  3. 丧失实时诊断数据

建议流程

  1. 先用三招救急临时止血
  2. 问题缓解后,在非高峰期优化参数
  3. 观察无告警后再逐步重启更新参数

Q6:如何避免 CPU 飙升的常见错误?
A:

  1. 代码 Bug:无限循环、死锁、频繁 GC → 代码审查、单元测试
  2. 坏查询:缺索引、全表扫描、大 JOIN → 定期 EXPLAIN 分析
  3. 配置不合理:缓冲池太小、线程数设置错误 → 按规范配置参数
  4. 硬件故障:磁盘坏块导致 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 板块与广大技术同仁交流探讨,共同学习成长。




上一篇:Nginx 502 Bad Gateway 故障排查:反向代理场景下的五大根因与修复
下一篇:Windows与Linux双系统安装完整指南:从分区到引导详解
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-3-29 06:26 , Processed in 0.520293 second(s), 41 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表