第 4 讲:事务隔离级别、脏读、不可重复读、幻读
核心结论(10 条必记)
- 事务隔离级别解决的是并发事务互相影响的问题
- 脏读是读到未提交数据
- 不可重复读是同一行前后读不一致
- 幻读是同条件范围查询结果集发生变化
- Read Uncommitted 隔离性最差,生产几乎不用
- Read Committed 可以避免脏读,但不能避免不可重复读
- Repeatable Read 可以避免不可重复读,是 MySQL 默认级别
- Serializable 隔离最强,但性能最差
- MVCC 主要用于 RC、RR 下的快照读
- MySQL 对幻读的处理是 MVCC + 锁机制共同完成的
一、并发事务的 3 类经典问题
脏读(Dirty Read)
读到另一个事务还没提交的数据。如果对方回滚,读到的就是无效数据。
T2: update balance 1000->900 (未提交)
T1: select balance -> 读到 900 (脏数据)
T2: rollback -> 恢复成 1000不可重复读(Non-repeatable Read)
同一事务内,同一行数据前后两次读取结果不同。
T1: select balance -> 1000
T2: update balance 1000->900; commit;
T1: select balance -> 900 (同一行变了)幻读(Phantom Read)
同一事务内,同条件范围查询结果集行数变化(多了或少了行)。
T1: select count(*) where user_id=1001 -> 5
T2: insert user_id=1001 的新订单; commit;
T1: select count(*) where user_id=1001 -> 6 (多了一行)区别速记
| 问题 | 核心表现 | 记忆法 |
|---|---|---|
| 脏读 | 读到未提交数据 | "不靠谱的数据" |
| 不可重复读 | 同一行前后不一致 | "同一行变了" |
| 幻读 | 范围查询行数变化 | "行的数量变了" |
二、四种事务隔离级别
| 隔离级别 | 脏读 | 不可重复读 | 幻读 | 说明 |
|---|---|---|---|---|
| Read Uncommitted | 可能 | 可能 | 可能 | 最弱,生产几乎不用 |
| Read Committed (RC) | 不会 | 可能 | 可能 | Oracle 默认 |
| Repeatable Read (RR) | 不会 | 不会 | 理论可能,MySQL 控制更强 | MySQL InnoDB 默认 |
| Serializable | 不会 | 不会 | 不会 | 最强,但并发性能差 |
隔离级别越高,一致性越强,但并发性能越低
三、RC vs RR 的核心区别
Read Committed (RC)
- 每次读取生成新的 Read View
- 读的是当前最新已提交版本
- 可能不可重复读(因为每次读都看最新)
Repeatable Read (RR)
- 事务第一次快照读时生成 Read View,之后一直复用
- 事务内多次读取结果一致
- 避免不可重复读
RC: 每次读 -> 新 Read View -> 可能看到别人已提交的更新
RR: 首次读 -> 固定 Read View -> 事务内始终一致四、MySQL 为什么默认用 RR?
- 一致性更强 -- 比 RC 多解决不可重复读
- MVCC 支撑 -- InnoDB 能高效实现 RR,性能损失不大
- 业务需要 -- 订单/库存/账户类业务希望事务内视图稳定
五、快照读 vs 当前读
| 类型 | SQL 示例 | 特点 |
|---|---|---|
| 快照读 | select * from user where id = 1 | 走 MVCC,不加锁,读某个可见版本 |
| 当前读 | select ... for update / update / delete / insert | 读最新版本,加锁,考虑锁冲突 |
MVCC 服务的对象
- 主要服务于 RC、RR 下的快照读
- 当前读要靠锁机制
六、RR 下幻读的争议
快照读场景
RR 借助 MVCC,第二次普通 select 通常看不到别人后插入的新行 -> 看起来避免了幻读
当前读场景
select ... for update / update 等读最新数据,需要靠间隙锁 + 临键锁阻止范围内插入新行
面试回答
标准 SQL 角度 RR 不能完全解决幻读,Serializable 才能彻底解决。MySQL InnoDB 中,普通快照读靠 MVCC 很多场景下看起来避免了幻读;当前读场景还需通过间隙锁、临键锁防止范围内插入,进一步控制幻读问题。
七、MySQL 并发控制的两条线
普通读一致性问题 -> 靠 MVCC 解决
RC: 每次读新快照
RR: 事务内复用快照
当前读下的并发插入/修改问题 -> 靠锁解决
行锁 / 间隙锁 / 临键锁MySQL 的事务隔离不是只靠 MVCC,也不是只靠锁,而是两者结合
八、面试高频题
1. 脏读/不可重复读/幻读区别?
脏读: 读到未提交数据 不可重复读: 同一事务内同一行前后读结果不同 幻读: 同一事务内同条件范围查询结果集行数变化
2. MySQL 默认隔离级别?
InnoDB 默认 Repeatable Read(RR)
3. RC 和 RR 区别?
RC 每次读已提交最新版本,可能不可重复读 RR 事务内复用同一个 Read View,避免不可重复读,一致性更强
4. RR 能完全解决幻读吗?
标准角度不能完全解决。MySQL 中快照读靠 MVCC,当前读靠间隙锁/临键锁共同控制。
练习题(待完成)
- [ ] 练习 1:用账户余额举例说明脏读
- [ ] 练习 2:不可重复读和幻读的区别
- [ ] 练习 3:为什么 RC 会出现不可重复读,而 RR 不会?
- [ ] 练习 4:MySQL 默认为什么用 RR 而不是 RC?
下一讲预告
第 5 讲:MVCC、Read View、快照读、当前读
- MVCC 具体实现机制
- 隐藏字段是什么
- undo log 版本链怎么形成
- Read View 是什么
- RC 和 RR 为什么行为不同
- 快照读和当前读到底怎么区分