Skip to content

MySQL 是如何实现事务的? ​

  • 标签:后端、MySQL、数据库

🏛 业务架构师 ​

MySQL 靠三大机制合力交付事务的 ACID,我按业务要什么保障对应着讲:

  • redo log(重做日志):先写日志再改数据页(WAL),宕机后重放恢复已提交修改——这是持久性的来源,业务含义是"支付成功就绝不丢",交易系统的信任底线;
  • undo log(回滚日志):记录修改前镜像,回滚靠它,MVCC 的版本链也是它——原子性的来源,业务含义是"转账扣了 A 没加 B 时能整体撤销",不会出现钱扣了货没发;
  • 锁 + MVCC:写写靠行锁互斥,读写靠 MVCC 快照不阻塞——隔离性的来源,业务含义是高并发下单时用户之间互不干扰、报表不影响交易。

再补一个关键细节:redo log 与 binlog 的两阶段提交保证两个日志一致,主从复制才不会错账——这是对账系统能成立的前提。

业务视角的结论:事务不是数据库白送的,它是延迟与吞吐的价格标签。每次 commit 都是日志落盘(可调 innodb_flush_log_at_trx_commit),强持久与高性能之间要按业务分级:支付库双 1 配置,日志库可放宽换吞吐。理解机制的真正价值是能给不同业务定不同的持久化档位,而不是把所有库都按最贵的配。

🏗 系统架构师 ​

我按 ACID 四个特性对应到 InnoDB 的实现机制:

  • 原子性 → undo log:事务修改前先记反向操作(insert 记 delete),回滚时逆序重放。undo log 同时是 MVCC 的版本链来源,一份数据两个用途;
  • 持久性 → redo log(WAL):先写日志后刷数据页,顺序写替代随机写,配合 binlog 做两阶段提交保证两者一致,宕机后 redo 重放恢复已提交事务;
  • 隔离性 → MVCC + 锁:读走快照(ReadView + undo 版本链)不加锁,写加行锁,RR 级别下用 next-key lock(行锁 + 间隙锁)防幻读;
  • 一致性:是前三者共同作用的结果,加上主键、唯一约束、外键等完整性约束兜底。

一条 update 的完整链路:Buffer Pool 改页 → 写 undo → 写 redo(prepare)→ 写 binlog → redo(commit)。崩溃恢复时 redo 处于 prepare 且 binlog 完整则提交,否则回滚——这套两阶段机制是 MySQL 在"性能(顺序日志)+ 正确性(崩溃一致)"之间的经典权衡,我在设计任何自研存储/状态机时都会参照 WAL + 2PC 这个骨架。

💻 全栈开发专家 ​

ACID 四个特性各有一个落地机制,我对着事务流程讲:

  • 原子性 → undo log:修改前先记反向操作(insert 记 delete)。回滚时逆序重放 undo,保证要么全做要么全不做。undo log 同时服务 MVCC。
  • 持久性 → redo log(WAL):先写日志再改数据页,崩溃后重放 redo 恢复已提交修改。顺序写代替随机写,配合 innodb_flush_log_at_trx_commit=1(每次提交刷盘)保证不丢。
  • 隔离性 → 锁 + MVCC:写写靠行锁/间隙锁,读写靠 MVCC 版本链(ReadView)互不阻塞。
  • 一致性:前三者共同保证 + 应用层约束(唯一索引、外键、业务校验)。

执行顺序(一条 update):

text
① 写 undo log  ② 修改 buffer pool 中的数据页
③ 写 redo log(prepare)  ④ 写 binlog  ⑤ redo log(commit)

③④⑤ 就是两阶段提交。生产必配"双 1":sync_binlog=1 + innodb_flush_log_at_trx_commit=1,否则机器断电可能丢已提交事务——我做过一次非双 1 配置导致主从数据不一致的复盘,教训深刻。