事务(ACID → MySQL 内部是怎么实现的)
我用一句话总结:
A、C、I、D 不是分开的,它们是 InnoDB 内部六大模块协同“跑出来”的效果。
下面我按“工程师视角”讲每一项在 MySQL 内部到底怎么实现的。
🧱 0. ACID 一句话工程版总结
| 字母 | 真正含义(工程师视角) | InnoDB 具体实现方式 |
|---|---|---|
| A(原子性) | 要么都成功,要么都失败 | Undo Log + 回滚机制 |
| C(一致性) | 事务前后数据约束必须满足 | 依赖开发者 + ACID 联合保证 |
| I(隔离性) | 不同事务互不干扰 | 锁(行锁/间隙锁) + MVCC |
| D(持久性) | 提交就不能丢 | Redo Log + WAL + Double Write |
1️⃣ A:原子性(Atomicity)—— Undo Log 实现的
工程视角就是一句话:
原子性 = 有撤销日志(Undo Log)→ 哪个 update 失败,就能按日志倒回去。
🧩 Undo Log 里有什么?
例如:
update user set age = 20 where id = 1;
Undo Log 会记录:
(表名 user, 主键 1, 修改前 age = 18)
如果事务失败,直接按 Undo Log 把数据恢复成 18。
🛠️ Undo Log 存在哪?
- 存在 Undo 页(Undo Page)
- 由 Rollback Segment 管理
- 是一条链(所谓 Undo 链)
💡 原子性在 MySQL 内部就一句话:
Undo Log 是“反向补丁(reverse patch)”。
写成功前提:事务还没 commit,随便回滚 → A 实现。
2️⃣ C:一致性(Consistency)—— ACID 联合作用 + 开发者保证
一致性不是 MySQL 单独保证的,它是一个最终结果:
你写的数据必须满足表结构约束(not null、unique、外键)与业务约束。
实现手段:
| 机制 | 保证什么一致性 |
|---|---|
| 原子性(Undo) | 更新失败不会只更新一半 |
| 隔离性(MVCC/锁) | 不被其他事务插手 |
| 持久性(Redo) | crash 之后恢复一致 |
| 外键、唯一约束 | 保证业务结构不乱 |
一致性依赖 ACID ALL,而不是某一个模块。
所以 C 是效果,不是一个独立组件。
3️⃣ I:隔离性(Isolation)—— 锁 + MVCC 实现的
这块你已经理解不少,但我给你用工程师视角浓缩句:
隔离性 = 读靠 MVCC,写靠锁。
避免互相干扰靠 next-key lock。
🔧 写操作:InnoDB 使用行锁体系
- 行锁(Record Lock)
- 间隙锁(Gap Lock)
- Next-Key Lock(Record + Gap)
实现方式:事务写行时加锁,别人不能改。
🔍 读操作:RC / RR 靠 MVCC
MVCC 用三样东西:
- undo log(历史版本)
- trx_id(每行都有版本号)
- read view(事务可见性判断)
RR / RC 的区别:
| 隔离级别 | 读取版本策略 |
|---|---|
| RC | 每次读都重新生成 ReadView |
| RR | 一个事务内共享同一个 ReadView |
一句话:
RR 读取的是“事务开始时的快照”,RC 读取的是“最新快照”。
因此没有幻读靠锁,而不是 MVCC。
4️⃣ D:持久性(Durability)—— Redo Log + WAL
核心机制一句话:
持久性不是靠把数据写入磁盘页,而是靠 Redo Log。
数据页可以慢写,但 redo 必须先落盘。
这就是 WAL:Write-Ahead Logging。
🔥 MySQL 持久性流程全图(极简工程师版)
事务执行:
update ...
流程:
- Buffer Pool 中修改页(脏页)
- 生成 Redo 日志(物理日志)
- Redo Log 先写到磁盘(顺序写,非常快)
- 客户端收到“提交成功”
- 后台慢慢把脏页(data page)Flush 到磁盘
也就是说:
👉 提交成功 ≠ 数据页落盘
👉 提交成功 = redo log 已落盘
Crash 后,根据 Redo Log 恢复一致状态 → D 实现。
⭐ ACID = 6 个核心组件一起跑出来的
| ACID | 内部实现核心 |
|---|---|
| A | Undo Log |
| C | Undo + Redo + MVCC + 锁 |
| I | 锁 + MVCC |
| D | Redo Log + WAL + DoubleWrite |
🔥 重点来了:ACID 四个特性不是分开工作的
它们不是线性的,而是像“组件树”一样协作:
事务开始
│
├── 修改 Buffer Pool
├── 写 Undo Log(为了原子性、MVCC)
├── 写 Redo Log(为了持久性)
├── 加锁(为了隔离性)
│
└── 提交 → 决定读写可见性
Undo 与 Lock 共同做隔离性
Undo 与 ReadView 共同做 MVCC
Redo 单独负责持久性
最终共同保证一致性
这就是 InnoDB 的 ACID。
浙公网安备 33010602011771号