线上MySQL突然卡死,排查的黄金5分钟

摘要:从一次"凌晨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和内存
五分钟紧急处理,降级重启扩容选


希望这篇文章能成为你的应急手册!保存下来,线上故障时翻出来照着做!💪