事务理论 - ACID
事务理论 - ACID
ACID 是数据库事务的四个基本特性:原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability)。它规定了"一组操作要么整体成功、要么整体失败"的语义,是并发环境下数据正确性的根基。
1. 什么是事务
事务(Transaction)是一组不可分割的数据库操作序列。最典型的例子是转账:A 向 B 转 100 元,需要"扣除 A 的余额"和"增加 B 的余额"两步,两步必须同时成功或同时失败,否则数据就错乱了。ACID 正是刻画这种"全有或全无 + 并发正确 + 失败可恢复"的四个特性。
2. ACID 四特性
- 原子性(Atomicity):事务是一个不可分割的整体,事务内的所有操作要么全部成功,要么全部失败回滚,不存在"做了一半"的中间态。
- 一致性(Consistency):事务执行前后,数据必须从一个一致状态转移到另一个一致状态。例如转账前后两人余额之和不变,不能出现"A 扣了钱、B 却没收到"的情况。
- 隔离性(Isolation):多个并发事务之间相互隔离、互不干扰,一个事务的中间状态对其他事务不可见。
- 持久性(Durability):事务一旦提交,对数据库的修改就永久保存,即使随后系统宕机也不会丢失。
一致性是目的,原子性、隔离性、持久性是达成目的的手段;其中隔离性在并发场景下最难完全保证,因此引入了"隔离级别"的折中(见第 4 节)。
3. ACID 靠什么保证
以 MySQL(InnoDB)为例,四大特性各有其支撑机制:
| 特性 | 保证机制 | 说明 |
|---|---|---|
| A 原子性 | undo log | 记录数据修改前的旧值,回滚时反向撤销已执行的操作 |
| C 一致性 | 代码 + 约束 | 由业务代码、主键/外键/唯一约束等共同保证,是"目标"而非单一机制 |
| I 隔离性 | MVCC + 锁 | 多版本并发控制提供一致性读,锁处理写冲突 |
| D 持久性 | redo log | 事务提交时先写 redo log 并刷盘,宕机后据此重放恢复 |
- undo log(回滚日志):逻辑日志,保存旧版本数据。回滚时执行反向操作,也用于 MVCC 读取历史版本。
- redo log(重做日志):物理日志,记录"在某个数据页上做了什么修改"。采用 WAL(Write-Ahead Logging)策略——先写日志、再写磁盘数据页,宕机重启后按 redo log 重放已完成的事务。它是持久性的关键,"提交即落盘"指的就是 redo log 刷盘。
一致性为什么"由代码保证"
一致性是业务语义层面的约束(如"余额不能为负"),数据库只能保证结构层面的约束(外键、唯一键等)。是否在扣款前校验余额、扣款与加款是否放在同一事务,都是业务代码的职责,因此一致性最终要靠正确的代码来实现。
4. 隔离级别与并发异常
当多个事务并发执行时,若不加约束,会出现三类读异常:
- 脏读(Dirty Read):读到了另一个事务尚未提交的数据;一旦对方回滚,读到的就是不存在的数据。
- 不可重复读(Non-repeatable Read):同一事务内两次读同一行,结果不同,因为期间另一个事务提交了修改。
- 幻读(Phantom Read):同一事务内两次执行相同的范围查询,第二次多(或少)了一些行,因为另一个事务提交了插入/删除。
SQL 标准定义了四个隔离级别,用强弱换并发性能:
| 隔离级别 | 脏读 | 不可重复读 | 幻读 | 说明 |
|---|---|---|---|---|
| 读未提交(Read Uncommitted) | 可能 | 可能 | 可能 | 几乎不加隔离,性能最高 |
| 读已提交(Read Committed, RC) | 不可能 | 可能 | 可能 | 只能读到已提交数据,Oracle/PostgreSQL 默认 |
| 可重复读(Repeatable Read, RR) | 不可能 | 不可能 | 标准允许可能 | MySQL InnoDB 默认 |
| 串行化(Serializable) | 不可能 | 不可能 | 不可能 | 最强,事务串行执行,并发最差 |
隔离级别越高,一致性与并发性越难两全。可重复读通过 MVCC 实现,在同一事务里始终读同一份快照;串行化则通过加锁让并发事务串行化。
4.1 MySQL InnoDB 的可重复读与幻读
MySQL InnoDB 在 RR 级别下,针对一致性读(普通 SELECT)利用 MVCC 返回事务开始时的快照,从而避免不可重复读。对于当前读(SELECT ... FOR UPDATE、UPDATE、DELETE),InnoDB 额外使用间隙锁(Gap Lock)+ 临键锁(Next-Key Lock)锁住记录及其间隙,从而在很大程度上避免了幻读。因此 InnoDB 的 RR 实际上已能规避大部分幻读场景,这也是它把默认级别设为 RR 的原因。
关于快照读的底层实现可参见 MySQL 事务与 MVCC。
5. 隔离级别的选择与实战
- 先看默认值:MySQL 默认 RR,Oracle/PostgreSQL 默认 RC,迁移或跨库开发时要留意差异。
- RC 常用于高并发:读已提交配合行锁,锁范围小、并发高,是很多互联网系统的实际选择,但业务需容忍不可重复读。
- 串行化只在必要时用:它的锁代价极高,通常只用于对一致性要求极端严格的少数操作。
- 缩短事务:事务内尽快提交、避免长事务,减少锁持有时间与 undo 膨胀。
- 明确隔离级别:在代码或用例中显式声明所需级别,不要依赖隐式默认。
ACID 属于事务理论的基础,与之相关的还有分布式场景下的 CAP / BASE,见 分布式理论 - CAP;事务基础概念见 事务理论 - 基础。
6. 小结
- 事务的四个特性是原子性、一致性、隔离性、持久性(ACID)。
- MySQL 中 A 靠 undo log、D 靠 redo log、I 靠 MVCC + 锁,C 最终由业务代码保证。
- 并发会带来脏读、不可重复读、幻读,隔离级别越高异常越少、并发越差。
- InnoDB 默认 RR:普通读走 MVCC 快照,当前读靠间隙锁规避幻读。
- 选隔离级别要在正确性与并发性之间权衡,并尽量缩短事务、显式声明级别。