MySQL默认隔离级别是REPEATABLE READ,主要依靠MVCC+间隙锁实现:快照读复用事务启动时的Read View,当前读加记录锁与间隙锁防止幻读;RC每次SELECT新建Read View;SERIALIZABLE因强制读加锁致性能骤降。

MySQL默认隔离级别是REPEATABLE READ,但不是靠锁实现的
很多人以为REPEATABLE READ靠的是行锁或表锁来保证多次读一致,其实InnoDB在该级别下主要依赖MVCC(多版本并发控制)+间隙锁(Gap Lock)组合。普通SELECT不加锁,靠的是Read View机制判断哪些版本可见;而UPDATE/DELETE等则会加记录锁+间隙锁防止幻读。
关键点在于:REPEATABLE READ下事务启动时创建Read View,之后所有快照读都复用它;而READ COMMITTED则是每次SELECT都新建Read View。
- RR级别下,事务中第二次
SELECT看到的数据和第一次完全一致,哪怕其他事务已提交修改 - RC级别下,第二次
SELECT可能看到新提交的数据,但不会出现脏读 - RR能避免幻读(通过间隙锁),RC不能——执行
INSERT可能触发唯一键冲突或死锁
MVCC的三个核心组件:DB_TRX_ID、DB_ROLL_PTR、Undo Log
MVCC不是独立模块,而是由每行记录隐含字段和Undo Log协同工作的结果。每条记录包含:
-
DB_TRX_ID:最后修改该行的事务ID -
DB_ROLL_PTR:指向Undo Log中前一版本的指针 - Undo Log里存着历史版本数据和对应
TRX_ID
当执行快照读时,InnoDB根据当前事务的Read View(包含m_ids活跃事务列表、min_trx_id、max_trx_id等)逐条比对记录的DB_TRX_ID,决定是否可见。规则简单说就是:DB_TRX_ID必须小于min_trx_id,或在m_ids中不存在且不大于max_trx_id。
立即学习“Java免费学习笔记(深入)”;
注意:MVCC只对快照读(普通SELECT)生效;当前读(SELECT ... FOR UPDATE、UPDATE、DELETE)会忽略MVCC,直接查最新版本并加锁。
为什么Serializable不是“最安全”,而是性能杀手
SERIALIZABLE看似终极隔离,实际是通过强制将所有普通SELECT重写为SELECT ... LOCK IN SHARE MODE来实现。这意味着读操作也要加S锁,写操作加X锁,彻底串行化。
- 高并发下极易出现锁等待甚至死锁
- 无法利用MVCC的无锁读优势,QPS断崖式下降
- 很多ORM框架(如MyBatis)默认不兼容该级别,可能抛出
Lock wait timeout exceeded
真实业务几乎不用SERIALIZABLE。需要强一致性时,更常用的是在REPEATABLE READ下配合SELECT ... FOR UPDATE显式加锁,粒度可控。
面试常问陷阱:RR级别真的完全避免幻读吗?
标准答案是“RR通过间隙锁避免了**幻读现象**”,但要注意这个“避免”是有前提的:
- 仅针对当前读(比如
SELECT ... FOR UPDATE后INSERT相同范围) - 快照读(普通
SELECT)本来就不会看到新插入行,谈不上幻读 - 如果先快照读,再当前读,再插入——间隙锁可能没覆盖到新插入位置,仍可能幻读
更关键的是:MySQL 8.0+在READ COMMITTED级别也支持间隙锁(用于外键检查和唯一约束),所以“间隙锁只在RR有”是过时认知。真正区别在于:RR默认启用间隙锁做范围保护,RC默认只锁记录本身(record lock),除非显式需要。
MVCC和隔离级别的底层联动非常细,调参或换引擎(比如换成MyRocks)会改变行为。别只背结论,得清楚每个TRX_ID怎么比较、每个UNDO版本何时清理、Read View何时复用——这些才是面试追问的落点。










