摘要:从一次"凌晨2点CEO在群里@所有人"的紧急故障出发,系统性总结MySQL应急排查的完整SOP。通过5分钟分步骤排查流程、6大常见故障场景的真实案例、以及紧急处理手段的决策树,揭秘如何快速定位死锁、慢查询、连接数爆满、磁盘空间满等问题。配合时序图展示排查流程,给出可直接执行的排查命令和应急处理脚本。
💥 翻车现场
2025年10月7日,凌晨2点38分。
哈吉米被一连串的消息吵醒。
钉钉群(全体@):
CEO:网站打不开了?
技术总监:数据库挂了,紧急处理!
运维:MySQL连接不上!
客服主管:用户投诉爆炸,支付失败!
哈吉米(冷汗):"完了完了……"
紧急打开监控:
告警:
🚨 MySQL连接数:2000(max_connections=1000,超了)
🚨 CPU:100%
🚨 慢查询:500条/分钟
🚨 所有接口:超时
哈吉米:"到底是哪出问题了?"
技术总监(私聊):"5分钟内必须恢复,否则损失百万!快速定位问题!"
哈吉米(慌乱):"我……我……"
第二天早上,南北绿豆和阿西噶阿西来复盘。
南北绿豆:"线上故障要有SOP(标准操作流程),按步骤排查,不能慌。"
阿西噶阿西:"来,我教你黄金5分钟的排查流程。"
⏱️ 黄金5分钟排查流程
南北绿豆在白板上画了一个流程图。
排查流程图
graph TD
A[告警:MySQL卡死] --> B[第1分钟:看当前查询]
B --> C[SHOW PROCESSLIST]
C --> D{发现问题?}
D -->|慢查询| E1[KILL慢查询]
D -->|死锁| E2[看下一步]
D -->|连接数满| E3[看下一步]
E2 --> F[第2分钟:看锁等待]
E3 --> F
F --> G[SHOW ENGINE INNODB STATUS]
G --> H{发现死锁?}
H -->|是| I[KILL死锁事务]
H -->|否| J[第3分钟:看慢查询日志]
J --> K[查看slow query log]
K --> L[定位慢SQL]
L --> M[KILL或优化]
M --> N[第4分钟:看系统资源]
N --> O[CPU/内存/磁盘]
O --> P{磁盘满?}
P -->|是| Q[清理binlog]
P -->|否| R[第5分钟:紧急处理]
R --> S[扩容/降级/重启]
style E1 fill:#90EE90
style I fill:#90EE90
style Q fill:#90EE90
style S fill:#FFE4B5
🔍 第1分钟:SHOW PROCESSLIST 看当前查询
核心命令
-- 查看当前正在执行的SQL
SHOW PROCESSLIST;
-- 或者查看完整SQL(不截断)
SHOW FULL PROCESSLIST;
输出分析
+-----+------+-------------------+------+---------+------+----------+------------------+
| Id | User | Host | db | Command | Time | State | Info |
+-----+------+-------------------+------+---------+------+----------+------------------+
| 123 | app | 192.168.1.10:3306 | shop | Query | 185 | updating | UPDATE order ... |
| 124 | app | 192.168.1.10:3306 | shop | Sleep | 300 | | NULL |
| 125 | app | 192.168.1.10:3306 | shop | Query | 95 | Sending | SELECT * FROM .. |
+-----+------+-------------------+------+---------+------+----------+------------------+
关键字段:
| 字段 | 含义 | 重点关注 |
|---|---|---|
| Id | 连接ID | 用于KILL |
| Time | 执行时间(秒) | > 10秒需要注意 |
| State | 当前状态 | updating、Locked、Sending data |
| Info | 正在执行的SQL | 定位慢查询 |
快速定位问题
-- 找出执行时间超过10秒的查询
SELECT
id,
user,
time,
state,
LEFT(info, 100) AS query
FROM information_schema.PROCESSLIST
WHERE time > 10
ORDER BY time DESC;
-- 找出正在等待锁的查询
SELECT * FROM information_schema.PROCESSLIST
WHERE state LIKE '%lock%';
紧急处理
发现慢查询 → KILL
-- KILL掉慢查询
KILL 123; -- 123是Id
-- 批量KILL(慎用)
SELECT CONCAT('KILL ', id, ';')
FROM information_schema.PROCESSLIST
WHERE time > 60
INTO OUTFILE '/tmp/kill.sql';
-- 执行
SOURCE /tmp/kill.sql;
🔍 第2分钟:SHOW ENGINE INNODB STATUS 看锁等待
核心命令
SHOW ENGINE INNODB STATUS\G
输出分析(关键部分)
------------
TRANSACTIONS
------------
Trx id counter 12345
...
---TRANSACTION 12343, ACTIVE 15 sec
2 lock struct(s), heap size 1136, 1 row lock(s)
MySQL thread id 123, query id 5678 updating
UPDATE account SET balance = 900 WHERE user_id = 10086
*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 58 page no 3 ...
关键信息:
| 信息 | 含义 |
|---|---|
| ACTIVE 15 sec | 事务活跃了15秒 |
| lock struct(s) | 持有的锁数量 |
| WAITING FOR THIS LOCK | 等待的锁 |
| LATEST DETECTED DEADLOCK | 最近的死锁信息 |
快速定位死锁
-- MySQL 8.0
SELECT
waiting_trx_id,
waiting_pid,
waiting_query,
blocking_trx_id,
blocking_pid,
blocking_query
FROM sys.innodb_lock_waits;
-- 结果
waiting_trx_id | waiting_query | blocking_query
---------------|------------------------|------------------------
12345 | UPDATE account ... | UPDATE account ...
紧急处理
发现死锁 → KILL其中一个事务
-- KILL死锁的事务
KILL 124;
🔍 第3分钟:查看慢查询日志
核心命令
-- 查看慢查询日志是否开启
SHOW VARIABLES LIKE 'slow_query%';
+---------------------+--------------------------------+
| Variable_name | Value |
+---------------------+--------------------------------+
| slow_query_log | ON |
| slow_query_log_file | /var/log/mysql/slow-query.log |
+---------------------+--------------------------------+
-- 查看慢查询阈值
SHOW VARIABLES LIKE 'long_query_time';
+-----------------+----------+
| Variable_name | Value |
+-----------------+----------+
| long_query_time | 1.000000 | ← 超过1秒的查询记录
+-----------------+----------+
分析慢查询日志
# 方法1:直接查看(最后100行)
tail -100 /var/log/mysql/slow-query.log
# 方法2:用mysqldumpslow分析
mysqldumpslow -s t -t 10 /var/log/mysql/slow-query.log
# 输出:
Count: 127 Time=15.23s (1934s) Lock=0.00s (0s) Rows=100.0 (12700)
SELECT * FROM order WHERE user_id = N
# 说明:这条SQL执行了127次,平均15.23秒
# 方法3:用pt-query-digest分析(推荐)
pt-query-digest /var/log/mysql/slow-query.log
# 输出TOP 10慢查询,按总耗时排序
紧急处理
定位到慢SQL → 优化或KILL
-- 找出正在执行这条慢SQL的连接
SELECT id, time, info
FROM information_schema.PROCESSLIST
WHERE info LIKE '%SELECT * FROM order WHERE user_id%';
-- KILL
KILL 125;
🔍 第4分钟:检查系统资源
核心命令
# CPU使用率
top
# 磁盘空间
df -h
# 磁盘IO
iostat -x 1
# 内存使用
free -m
常见问题
问题1:磁盘空间满
df -h
Filesystem Size Used Avail Use% Mounted on
/dev/sda1 500G 498G 2.0G 99% / ← 磁盘满了
# 排查:通常是binlog占用太多空间
du -sh /var/lib/mysql/mysql-bin.*
15G mysql-bin.000001
18G mysql-bin.000002
20G mysql-bin.000003
...
紧急处理:
-- 清理binlog(保留最近3天)
PURGE BINARY LOGS BEFORE DATE_SUB(NOW(), INTERVAL 3 DAY);
-- 或者手动删除(危险,慎用)
-- rm /var/lib/mysql/mysql-bin.000001
问题2:连接数爆满
-- 查看当前连接数
SHOW STATUS LIKE 'Threads_connected';
+-------------------+-------+
| Variable_name | Value |
+-------------------+-------+
| Threads_connected | 1523 | ← 当前连接数
+-------------------+-------+
-- 查看最大连接数
SHOW VARIABLES LIKE 'max_connections';
+-----------------+-------+
| Variable_name | Value |
+-----------------+-------+
| max_connections | 1000 | ← 最大连接数(超了)
+-----------------+-------+
紧急处理:
-- 临时提高连接数(重启后失效)
SET GLOBAL max_connections = 2000;
-- 或者KILL掉Sleep的连接
SELECT CONCAT('KILL ', id, ';')
FROM information_schema.PROCESSLIST
WHERE command = 'Sleep' AND time > 300; -- Sleep超过5分钟
问题3:CPU 100%
# 查看MySQL的CPU占用
top -p $(pidof mysqld)
# 通常原因:
# 1. 慢查询(全表扫描)
# 2. 大量并发查询
# 3. 死锁导致大量等待
紧急处理:
-- 查看正在执行的查询
SHOW PROCESSLIST;
-- KILL高CPU的查询
KILL xxx;
🔍 第5分钟:紧急处理决策
决策树
graph TD
A[问题仍未解决] --> B{是否能定位到问题SQL?}
B -->|是| C[KILL问题SQL]
B -->|否| D{连接数是否满?}
D -->|是| E[提高max_connections]
D -->|否| F{磁盘是否满?}
F -->|是| G[清理binlog]
F -->|否| H{是否死锁?}
H -->|是| I[KILL死锁事务]
H -->|否| J{服务是否可降级?}
J -->|是| K[降级非核心功能]
J -->|否| L{是否可重启?}
L -->|是| M[重启MySQL 最后手段]
L -->|否| N[扩容/主从切换]
C --> O[观察恢复情况]
E --> O
G --> O
I --> O
K --> O
M --> O
N --> O
style M fill:#FFB6C1
style N fill:#FFE4B5
紧急处理手段
手段1:KILL查询
-- KILL单个查询
KILL 123;
-- KILL多个查询(生成脚本)
SELECT CONCAT('KILL ', id, ';')
FROM information_schema.PROCESSLIST
WHERE time > 60;
手段2:降级非核心功能
// 应用层降级
@GetMapping("/order/list")
public Result listOrders() {
// 检查降级开关
if (degradeSwitch.isEnabled("order.list")) {
return Result.error("系统繁忙,请稍后再试");
}
// 正常查询
return orderService.listOrders();
}
手段3:临时提高连接数
SET GLOBAL max_connections = 2000;
手段4:清理binlog
-- 清理3天前的binlog
PURGE BINARY LOGS BEFORE DATE_SUB(NOW(), INTERVAL 3 DAY);
手段5:重启MySQL(最后手段)
# 慎重!会断开所有连接
systemctl restart mysqld
# 或者优雅重启
mysqladmin -uroot -p shutdown
systemctl start mysqld
🚨 6大常见故障场景
场景1:死锁导致大量等待
现象:
SHOW PROCESSLIST;
大量连接状态:updating、Locked
排查:
SHOW ENGINE INNODB STATUS\G
-- 查看LATEST DETECTED DEADLOCK部分
解决:
-- KILL死锁的事务
KILL 124;
场景2:慢查询打爆CPU
现象:
CPU:100%
慢查询:大量全表扫描
排查:
-- 找出慢查询
SELECT id, time, info
FROM information_schema.PROCESSLIST
WHERE time > 5
ORDER BY time DESC;
解决:
-- KILL慢查询
KILL 125;
-- 后续优化:加索引
CREATE INDEX idx_user_id ON order_info(user_id);
场景3:连接数爆满
现象:
ERROR 1040 (HY000): Too many connections
排查:
-- 当前连接数
SHOW STATUS LIKE 'Threads_connected'; -- 1523
-- 最大连接数
SHOW VARIABLES LIKE 'max_connections'; -- 1000
-- 查看连接来源
SELECT
user,
host,
COUNT(*) AS connection_count
FROM information_schema.PROCESSLIST
GROUP BY user, host
ORDER BY connection_count DESC;
解决:
-- 临时提高连接数
SET GLOBAL max_connections = 2000;
-- KILL Sleep连接
SELECT CONCAT('KILL ', id, ';')
FROM information_schema.PROCESSLIST
WHERE command = 'Sleep' AND time > 300;
场景4:磁盘空间满
现象:
ERROR 3 (HY000): Error writing file (Errcode: 28 - No space left on device)
排查:
df -h
# /dev/sda1 99% ← 磁盘满了
# 查看大文件
du -sh /var/lib/mysql/*
15G ibdata1
20G mysql-bin.000123
18G mysql-bin.000124
...
解决:
-- 清理binlog
PURGE BINARY LOGS BEFORE DATE_SUB(NOW(), INTERVAL 1 DAY);
-- 或者清理slow query log
-- 或者删除不用的表
场景5:主从延迟导致读写不一致
现象:
用户反馈:刚下单,查不到订单
排查:
-- 从库执行
SHOW SLAVE STATUS\G
Seconds_Behind_Master: 15 ← 延迟15秒
解决:
// 强制走主库
@DataSource("master")
public Order getOrderById(Long orderId) {
return orderMapper.selectById(orderId);
}
场景6:大量INSERT导致锁等待
现象:
批量导入数据,导致其他查询全部阻塞
排查:
-- 查看正在执行的INSERT
SELECT id, time, info
FROM information_schema.PROCESSLIST
WHERE info LIKE 'INSERT%'
ORDER BY time DESC;
解决:
-- KILL批量INSERT
KILL 126;
-- 或者等待完成(如果快完成了)
🛠️ 应急处理工具脚本
脚本1:一键排查
#!/bin/bash
# mysql_health_check.sh
echo "========== MySQL健康检查 =========="
# 1. 连接数
echo "1. 连接数:"
mysql -uroot -p123456 -e "SHOW STATUS LIKE 'Threads_connected';"
mysql -uroot -p123456 -e "SHOW VARIABLES LIKE 'max_connections';"
# 2. 慢查询
echo "2. 当前慢查询:"
mysql -uroot -p123456 -e "SELECT id, user, time, LEFT(info, 50) FROM information_schema.PROCESSLIST WHERE time > 5;"
# 3. 锁等待
echo "3. 锁等待:"
mysql -uroot -p123456 -e "SELECT * FROM information_schema.INNODB_LOCK_WAITS;"
# 4. 磁盘空间
echo "4. 磁盘空间:"
df -h | grep -E 'Filesystem|/dev/sda1'
# 5. CPU
echo "5. CPU使用率:"
top -bn1 | grep "Cpu(s)"
echo "========== 检查完成 =========="
脚本2:批量KILL慢查询
#!/bin/bash
# kill_slow_queries.sh
# KILL执行时间超过60秒的查询
mysql -uroot -p123456 -e "
SELECT CONCAT('KILL ', id, ';')
FROM information_schema.PROCESSLIST
WHERE time > 60 AND command != 'Sleep'
" | grep KILL | mysql -uroot -p123456
echo "慢查询已清理"
🎓 面试标准答案
题目:线上MySQL突然很慢,怎么排查?
答案:
黄金5分钟流程:
第1分钟:SHOW PROCESSLIST 看当前查询
- 找出慢查询(time > 10秒)
- KILL慢查询
第2分钟:SHOW ENGINE INNODB STATUS 看锁等待
- 查看是否有死锁
- KILL死锁事务
第3分钟:查看慢查询日志
- 用mysqldumpslow分析
- 定位高频慢SQL
第4分钟:检查系统资源
- CPU、内存、磁盘
- 磁盘满了清理binlog
第5分钟:紧急处理
- 提高连接数
- 降级非核心功能
- 重启MySQL(最后手段)
常见问题:
- 死锁 → KILL事务
- 慢查询 → KILL或加索引
- 连接数满 → 提高max_connections
- 磁盘满 → 清理binlog
- 主从延迟 → 强制主库
🎉 结束语
早上7点,哈吉米终于恢复了数据库。
哈吉米:"原来排查要有步骤,不能慌!"
南北绿豆:"对,线上故障最怕的是乱,按SOP一步步排查,5分钟能定位大部分问题。"
阿西噶阿西:"记住:SHOW PROCESSLIST看查询、SHOW ENGINE INNODB STATUS看锁、慢查询日志看SQL。"
哈吉米:"还有要提前准备好脚本,紧急时直接执行!"
南北绿豆:"对,平时演练,临时不慌!"
记忆口诀:
一分钟看PROCESSLIST,慢查询立马KILL
二分钟看INNODB STATUS,死锁事务快处理
三分钟查慢查询志,定位SQL是关键
四分钟看系统资源,磁盘CPU和内存
五分钟紧急处理,降级重启扩容选
希望这篇文章能成为你的应急手册!保存下来,线上故障时翻出来照着做!💪