之前一条磁盘告警让我连续盯了两晚的监控大屏。InnoDB 回滚段所在表空间从 40% 一路涨到 85%,业务侧开始出现大量更新等待,前台订单状态迟迟不刷新。登上去查了一圈,罪魁是一个已经挂了两小时没提交的事务:它中间改了三十多万行,期间所有新产生的旧版本都被它“拖住”,purge 线程根本清不动,回滚段自然越胀越大。
这件事让我一直想写一篇把 MySQL 事务讲透的文章。很多后端开发对事务的理解停留在“begin / commit / rollback 三条命令”的层面,但线上真正出问题时,拼的全是底层机制:redo、undo、锁、隔离级别、MVCC 怎么协作,事务边界怎么设计。这篇我会先把这套拼图讲清楚,再用两个真实故障复盘怎么排查长事务和死锁,最后给出事务优化和日常巡检的最佳实践。无论你是后端、DBA 还是运维,应该都能从里面找到直接能用的 SQL 和排查思路。
1. MySQL事务的底层拼图:redo、undo、锁与MVCC
1.1 先把“持久性”讲透:redo log的WAL设计
我见过很多人会对一个现象感到困惑:一条 update 执行完,明明数据页还没落盘,为什么 MySQL 敢告诉客户端“提交成功”?
靠的就是 WAL,Write-Ahead Logging。InnoDB 对数据页的修改,并不会在每次提交时都把随机脏页刷到磁盘,而是先把这次修改对应的日志顺序写到 redo log 文件里。数据页跟 redo log 的关系,可以类比成你写毕业论文:正文“页面”太多,不可能每次都完整拷到U盘保存;但你有一个小本子,每次改动都按顺序记下来。只要小本子在,哪怕电脑突然断电,等重启后也能照着记录把论文恢复到断电前的状态。
所以 redo log 解决的是持久性,核心动作是“顺序写”。顺序写比随机写快几个数量级,这也是为什么 MySQL 能承受很高的写入 TPS。
这里有个参数直接决定安全边界:innodb_flush_log_at_trx_commit。默认值是 1,表示每次事务提交都要把 redo log 刷到磁盘,这时候只要磁盘没坏,已提交事务不会丢。如果改成 0 或 2,崩溃时确实可能丢最近一小段已提交事务,具体我在第 4 章里展开讲。
再看一个常见误区:有人以为 redo log 越大越好,于是直接改到 8G,理由是避免频
