P33 MySQL 的隔离级别实现原理 MVCC
面试题:MySQL 的四种隔离级别是什么?MVCC 是怎么实现隔离的?
1. 四种隔离级别
| 隔离级别 | 脏读 | 不可重复读 | 幻读 |
|---|---|---|---|
| 读未提交(READ UNCOMMITTED) | 可能 | 可能 | 可能 |
| 读已提交(READ COMMITTED) | 解决 | 可能 | 可能 |
| 可重复读(REPEATABLE READ,MySQL 默认) | 解决 | 解决 | 基本解决(InnoDB) |
| 串行化(SERIALIZABLE) | 解决 | 解决 | 解决 |
InnoDB 的 RR 级别通过 MVCC + 间隙锁 额外解决了幻读(标准 SQL 里 RR 是允许幻读的,这是面试易错点)。
2. 三个并发问题的定义
- 脏读:读到其他事务未提交的数据;
- 不可重复读:同一事务内两次读同一行,结果不一样(其他事务提交了修改);
- 幻读:同一事务内两次范围查询,行数不一样(其他事务插入了新行)。
3. MVCC 的核心结构
MVCC(多版本并发控制)= 每行记录保存多个版本,读的时候只看自己事务可见的版本,读写互不阻塞。
① 隐藏字段
InnoDB 每行有三个隐藏列:
DB_TRX_ID:最近一次修改该行的事务 ID;DB_ROLL_PTR:回滚指针,指向 undo log 里的旧版本;DB_ROW_ID:无主键时的隐藏主键。
② undo log 版本链
每次修改不是覆盖旧值,而是把旧值链到 undo log,形成版本链:
text
最新版本 → 旧版本1 → 旧版本2 → ...③ ReadView(读视图)
事务开始快照读时生成 ReadView,记录:
- 当前未提交事务的最小 ID(
m_ids中最小); - 已创建的最大事务 ID;
- 当前事务自己的 ID。
判断版本可见性:某版本的事务 ID 在 ReadView 的活跃列表里,则不可见,沿版本链找上一个版本;直到找到"生成 ReadView 时已提交"的版本。
4. RC 与 RR 的区别(面试必问)
- RC(读已提交):每次 SELECT 都生成新的 ReadView → 能看到其他事务新提交的数据 → 不可重复读;
- RR(可重复读):事务第一次快照读时生成 ReadView,整个事务复用 → 读到的永远是快照时刻的数据 → 可重复读。
这就是两种隔离级别在 MVCC 下的本质差别:ReadView 的生成时机。
5. 当前读与加锁
MVCC 解决的是快照读;SELECT ... FOR UPDATE、UPDATE、DELETE 是当前读,读最新版本并加锁:
- 行锁(Record Lock)+ 间隙锁(Gap Lock)+ 临键锁(Next-Key Lock)防幻读;
- 所以 RR 下靠"MVCC(快照读)+ Next-Key 锁(当前读)"双保险。
一句话总结
MySQL 默认 RR 隔离;MVCC 通过隐藏列(事务ID+回滚指针)+ undo 版本链 + ReadView 让快照读不阻塞,RC 每次 SELECT 新建 ReadView、RR 复用首次 ReadView,因此 RR 可重复读;当前读再配合 Next-Key 锁解决幻读。