事务凭什么「要么全成、要么全不」,多事务并发为什么不出乱子。
| 性质 | 含义 | 破坏者 |
|---|---|---|
| A 原子性 | 事务内操作要么全做、要么全不做 | 中途崩溃(第 8 章恢复解决) |
| C 一致性 | 把库从一个一致状态带到另一个一致状态 | 约束被违反 |
| I 隔离性 | 并发事务互不干扰,像串行执行 | 并发异常(本章主题) |
| D 持久性 | 一旦提交,结果永久保存 | 掉电、介质损坏(第 8 章) |
原子性靠 undo,持久性靠 redo——详见第 8 章。本章聚焦 I(隔离性) 与并发下的 A/C 维系。
| 异常 | 描述 | 示例 |
|---|---|---|
| 脏读 | 读到别的事务未提交的修改 | T2 读到 T1 改了但还没 commit 的余额 |
| 不可重复读 | 同一事务两次读,值被别的事务改了 | 两次读余额不一样 |
| 幻读 | 同一查询两次,行集合变了(多/少了行) | 第二次多出别的事务插入的行 |
| 丢失更新 | 两个事务都改同一值,一个覆盖另一个 | 两人同时扣款,一笔被吞 |
根因:事务交叉执行破坏「串行等价」。解法:封锁或 MVCC。
S 锁 X 锁
S 锁 兼容 冲突
X 锁 冲突 冲突
两段锁协议(2PL):事务分「增长段」(只加锁)和「缩减段」(只放锁),先统一加锁、后统一释放。遵守 2PL 保证可串行化,但不解决死锁(靠死锁检测/超时/等待图)。
| 级别 | 脏读 | 不可重复读 | 幻读 | 实现 |
|---|---|---|---|---|
| READ UNCOMMITTED | 可能 | 可能 | 可能 | 几乎不锁 |
| READ COMMITTED | 杜绝 | 可能 | 可能 | 读快照/行锁提交即放 |
| REPEATABLE READ | 杜绝 | 杜绝 | 可能* | 快照读 + 间隙锁 |
| SERIALIZABLE | 杜绝 | 杜绝 | 杜绝 | 全串行/严格 2PL |
*MySQL InnoDB 在 REPEATABLE READ 下靠间隙锁(gap lock)基本杜绝幻读。隔离级别越高,一致性越强,并发吞吐越低。
MySQL InnoDB 与 PostgreSQL 靠 MVCC 而非全程加锁:
undo 日志里保留每一行的历史版本,链成一个版本链
读事务看到的是「事务开始时刻的快照」——
版本 trx_id ≤ 当前活跃最小事务号,且未被活跃事务修改
trx_id(最后修改者)和回滚指针(指向 undo 旧版本)。对比:四个隔离级别 + MVCC——READ UNCOMMITTED 几乎无保护、吞吐最高;READ COMMITTED 是 Oracle/PG 默认,避免脏读但允许不可重复读;REPEATABLE READ 是 InnoDB 默认,快照读 + 间隙锁兼顾一致与吞吐,OLTP 甜点;SERIALIZABLE 最安全最慢。MVCC 让读走快照免锁——用「多版本 + undo 空间」换「少加锁 + 高并发」。