跳转到内容

事务与 ACID 实现

用途:理解“提交后为什么不会丢”“崩溃后怎么恢复”

特性 含义 InnoDB 实现
原子性 A 全做或全不做 undo log(回滚)
一致性 C 数据满足业务约束 由应用 + 约束 + 隔离级别共同保证(不是引擎自动给的)
隔离性 I 并发事务互不干扰 锁 + MVCC
持久性 D 提交后不丢 redo log(WAL)+ 双写缓冲

事务提交时只需保证 redo log 落盘(顺序写,快),数据页可以之后由后台线程刷盘(随机写,慢)。崩溃后重放 redo 恢复已提交但未落盘的修改。

两阶段提交解决 redo 与 binlog 的一致性:

1 准备 redo(prepare,写 redo 并处于 prepare 状态)
2 写 binlog
3 redo 提交(commit)
  • 崩溃在 1、2 之间:binlog 不完整 → 主从可能不一致,故 binlog 落盘与 redo prepare 顺序必须严格。
  • 崩溃在 2、3 之间:binlog 已完整 → 恢复时按 binlog 提交(binlog 为准)。
  • 配置 innodb_flush_log_at_trx_commit=1 + sync_binlog=1 是“双 1”,最安全、性能最低。
  1. 长事务:undo 版本链无法清理 → 回滚段膨胀、MVCC 读放大、锁持有时间长、DDL 阻塞(MDL)。
  2. 大事务:binlog 单事件过大影响复制;回滚代价高;主从延迟。
  3. 事务中的远程调用:把网络耗时算进持锁时间,是吞吐塌陷的常见根因。

经验规则:事务内只做必要的数据库读写,不做 RPC、文件 IO、发消息;发消息用本地消息表(Outbox)与事务同库落盘。

引擎提供的是“并发读写的可见性规则”,业务约束(余额非负、状态不可回退、唯一性)必须由应用模型 + 数据库约束(唯一索引、外键、CHECK)+ 并发控制共同保证。仅靠 REPEATABLE READ 不会自动阻止重复插入。

误区 修正
自动提交关掉就“性能更好” 长连接空闲事务带来锁与回滚段问题
用 @Transactional 包住整个用例含外部调用 拆分:外部调用移出事务,或改异步补偿
认为 redo 落盘=数据落盘 数据页异步刷盘,靠 redo 恢复
双 1 一定需要 按业务可丢失窗口评估,日志类可放宽 flush_log_at_trx_commit=2
大事务拆小没意义 拆小能显著降低锁等待与复制延迟
  • MySQL 官方文档:InnoDB Transaction Model、The InnoDB Redo Log、Two-Phase Commit
  • 《高性能 MySQL》第 7、10 章
  • 丁奇《MySQL 实战 45 讲》日志与事务章节