04.事务隔离级别与MVCC
文章目录
问题: MySQL的四种隔离级别分别解决什么问题?InnoDB默认的RR级别下,Read View的生成规则是什么?它是如何结合MVCC和锁机制彻底解决幻读的?
👇 点击展开/折叠 面试背诵模板(建议先背这个)
一、四种隔离级别与并发问题对比
| 隔离级别 | 脏读 (Dirty Read) | 不可重复读 (Non-Repeatable Read) | 幻读 (Phantom Read) | 说明 |
|---|---|---|---|---|
| Read Uncommitted(未提交读) | ✅ 可能 | ✅ 可能 | ✅ 可能 | 存在脏读,几乎不使用 |
| Read Committed(读已提交,RC) | ❌ 解决 | ✅ 可能 | ✅ 可能 | Oracle等默认,每次SELECT生成新Read View |
| Repeatable Read(可重复读,RR) | ❌ 解决 | ❌ 解决 | ✅ InnoDB解决 | MySQL默认,事务内第一次SELECT时生成Read View |
| Serializable(串行化) | ❌ 解决 | ❌ 解决 | ❌ 解决 | 强制事务串行执行,性能最低 |
二、Read View(一致性视图)在RR下的生成与可见性规则
生成时机:在RR隔离级别下,Read View在事务内执行第一个快照读(普通SELECT语句)时生成,而非事务启动时。此后整个事务期间复用同一个Read View,直到事务结束,从而保证可重复读。
核心数据结构:
- m_ids:生成Read View时当前系统中所有活跃事务的ID列表(未提交事务)。
- min_trx_id:m_ids中的最小值(低水位)。
- max_trx_id:生成Read View时系统尚未分配的下一个事务ID(高水位)。
可见性判断规则(对某行记录的事务ID trx_id):
- 若
trx_id < min_trx_id:该事务在Read View生成前已提交,可见。 - 若
trx_id >= max_trx_id:该事务在Read View生成后启动,不可见。 - 若
min_trx_id <= trx_id < max_trx_id:若trx_id在m_ids中(活跃中),则不可见;若不在(已提交),则可见。
注意:RC级别与RR的核心区别在于,RC下每次SELECT都会重新生成Read View,而RR下复用同一个,这就是“可重复读”和“读已提交”的本质差异。
三、MVCC与Next-Key Lock协同彻底解决幻读
幻读指同一事务内,两次执行相同范围的当前读(如 SELECT * FROM t WHERE id > 10 FOR UPDATE)时,结果集行数不一致(其他事务插入了新数据)。
InnoDB在RR下采用分层防御:
① 快照读(MVCC层):普通的 SELECT(无 FOR UPDATE / LOCK IN SHARE MODE)通过复用Read View读取已提交快照,即使其他事务插入了新行,由于新行的事务ID大于Read View的 max_trx_id,对当前事务不可见,因此普通SELECT不会出现幻读。
② 当前读(锁机制层):对于 SELECT ... FOR UPDATE、UPDATE、DELETE 等当前读操作,InnoDB采用 Next-Key Lock = 行锁(Record Lock)+ 间隙锁(Gap Lock)。
- 行锁:锁定索引记录本身。
- 间隙锁:锁定索引记录之间的“间隙”(包括边界,防止新数据插入)。
具体作用机制:若执行 SELECT * FROM t WHERE id BETWEEN 10 AND 20 FOR UPDATE,InnoDB不仅锁定 id=10 和 id=20 的行,还会锁定 (10, 20) 之间的所有间隙。此时,其他事务若尝试在该间隙内插入 id=15,会被间隙锁阻塞,直到当前事务释放锁。正是这种物理锁机制,确保了第二次当前读时,范围内不会出现“凭空插入”的新行,从而彻底解决了幻读问题。
四、核心总结与易混淆点澄清
- RR下普通SELECT无幻读:靠MVCC复用Read View实现。
- RR下当前读(增删改查FOR UPDATE)无幻读:靠Next-Key Lock在物理上阻塞并发插入实现。
- 注意区分不可重复读与幻读:不可重复读针对已存在行的字段值变化(靠MVCC/行锁解决),幻读针对结果集行数的变化(靠间隙锁解决)。InnoDB的RR是唯一一个能通过MVCC+间隙锁组合解决幻读的默认隔离级别实现。