一个 UPDATE 语句卡 27 分钟之MySQL 事务踩坑日记

1. 事情是这样的

今天遇到一个怪事:测试环境中,kms项目有个定时任务,每5秒执行一次,本来跑得好好的,突然就不动了

最后一条日志停在:

18:06:13.594 - 登录失败,更新同步游标,now: 1773914773

之后就再也没动静了。

2. 开始破案(一步一步发现问题)

第1步:先看数据库里有什么妖孽

SELECT * FROM information_schema.INNODB_TRX;

看到一个妖孽事务

事务ID: 139630724
开始时间: 18:06:13
当前状态: RUNNING
在干嘛: UPDATE kss_log_sync_cursor SET last_sync_time = ... where id =1

安? 一个 UPDATE 语句执行了十几分钟还没完?这不太对劲!

第2步:加个监控盯着它

我写了个监控程序,每分钟检查一次长事务:

@Component
public class TransactionMonitor {
    @Scheduled(fixedDelay = 60000)  // 每分钟查一次
    public void checkLongRunningTransactions() {
        // 查超过5分钟的事务
        // 查到就告警
    }
}

监控开始报警了:

18:11:22 - 发现长事务,已运行5分钟
18:12:22 - 已运行6分钟
18:13:22 - 已运行7分钟
...
18:33:22 - 已运行27分钟

同一个事务,一直没变,一直在执行那条 UPDATE!

第3步:看这个线程在干嘛

SHOW PROCESSLIST;

看到线程29的状态:waiting for handler commit

翻译成人话:“我已经执行完UPDATE了,正在等提交完成”

第4步:看 InnoDB 内部状态(关键!)

SHOW ENGINE INNODB STATUS

看到一句话:

---TRANSACTION 139630724, ACTIVE (PREPARED) 353 sec

PREPARED 是啥意思?

第5步:弄明白事务提交到底干了啥

原来 MySQL 提交一个事务,不是一秒就完事的,它要分三步走:

你执行 UPDATE
    ↓
你敲 COMMIT
    ↓
【第一步】InnoDB说:我准备好了 (PREPARE)
    ↓
【第二步】写 Binlog(二进制日志) ← 这里需要磁盘空间!
    ↓
【第三步】InnoDB说:提交完成 (COMMIT),锁释放了

PREPARED 状态 就是:

  • 第一步已经完成
  • 正在做第二步(写 Binlog)
  • 锁还捏在手里不放

第6步:那为啥第二步卡住了?

联系运维查看mysql 磁盘使用情况,已经打算看binlog写入的文件系统是不是有问题了。。。心想mysql内部应该不能出bug吧。

df -h

结果看到:磁盘可用为0!为0!!!

破案了!磁盘满了!

Binlog 写不进去,就一直卡在 PREPARED 状态,锁不释放,其他事务干瞪眼。

3. 真相大白

完整的故事是这样的:

  1. 定时任务执行到 UPDATE kss_log_sync_cursor
  2. UPDATE 执行成功
  3. 自动提交事务(或者代码里发了 COMMIT)
  4. InnoDB 进入 PREPARED 状态
  5. 要写 Binlog 了 → 磁盘满了!写不进去!
  6. 事务卡死,锁不释放
  7. 后续的定时任务想更新同一行 → 被阻塞
  8. 监控开始报警,眼睁睁看着这个事务从5分钟跑到27分钟

4. 怎么解决

第一步:紧急处理

# 1. 清理磁盘空间(删掉旧 binlog)
mysql> PURGE BINARY LOGS BEFORE DATE(NOW() - INTERVAL 3 DAY);

# 2. 杀掉卡死的事务
mysql> KILL 29;

# 3. 再看磁盘空间
df -h  # 应该有空余了

第二步:长期预防

# 在 my.cnf 里加上
[mysqld]
binlog_expire_logs_seconds = 259200   # binlog 保留3天自动删

第三步:监控报警

# 写个脚本监控磁盘
df -h /var/lib/mysql | awk 'NR==2 {if ($5+0 > 80) print "磁盘快满了!"}'

5. 经验

以后排查这类问题,按这个顺序来:

  1. 先看磁盘df -h)—— 最简单,最容易被忽略
  2. 再看长事务INNODB_TRX
  3. 看进程状态PROCESSLIST
  4. 看 InnoDB 状态ENGINE INNODB STATUS

关键线索总结

看到啥 说明啥
waiting for handler commit 提交卡住了
ACTIVE (PREPARED) 卡在写 Binlog
df -h 100% 找到凶手了!

记一笔:

PREPARED + waiting for handler commit = 先看磁盘满了没

6. 最后说一句

谁能想到,一个最简单的 UPDATE 语句,会因为磁盘满了卡 27 分钟?

以后再遇到数据库卡死,先 df -h,别一上来就怀疑代码。

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容