☰
MySQL 事务隔离级别实现原理
2026/10/9 5:44:21 网站建设 项目流程

MySQL InnoDB 四种事务隔离级别实现原理

InnoDB 隔离级别基于MVCC (多版本并发控制)+锁(行锁、Gap 临键锁)共同实现; MVCC 主要解决读不加锁(快照读);锁用来解决当前读的幻读问题。 SQL 标准 4 个隔离级别(由低到高):

  1. READ UNCOMMITTED 读未提交
  2. READ COMMITTED 读已提交 RC
  3. REPEATABLE READ 可重复读 RR(MySQL InnoDB 默认级别)
  4. SERIALIZABLE 串行化

前置基础概念

1)快照读 vs 当前读

  • 快照读 (snapshot read):普通 select,读取 undo log 历史版本,不加行锁,依靠 MVCC。
select * from t where id=1;
  • 当前读 (current read):加锁读取最新版本,走行锁 / 临键锁,读取磁盘最新数据。
select ... for update; select ... lock in share mode; update / delete / insert

2)MVCC 核心部件:undo log + read‑view + 隐藏列

每行记录 3 个隐藏列:

  1. DB_TRX_ID:最后修改该行的事务 ID
  2. DB_ROLL_PTR:回滚指针,指向 undo log 里旧版本,形成版本链
  3. DB_ROW_ID:隐式主键(无主键时)

Read‑View(读视图):快照读的时候生成的一个视图对象,保存活跃事务 ID 集合,用来判断版本链上哪个版本对当前事务可见。

Read‑View 生成时机是 RC 和 RR 最核心区别!


1. READ UNCOMMITTED 读未提交

  • 原理:不使用 MVCC,不生成 Read‑View
  • 事务可以直接读到别的事务还没 commit 的数据(脏读)
  • 读取直接拿最新行,不去 undo 版本链找历史快照
  • 几乎生产不用。

问题:脏读 ✔,不可重复读 ✔,幻读 ✔

2. READ COMMITTED(RC)读已提交

✅实现原理

  1. 每次快照读(每执行一次普通 select,就重新生成一个新 Read‑View)
  2. 用 Read‑View 过滤 undo 版本链,只能读取已经提交事务所做的变更。

现象:

同一个事务内,前后两次相同select,中间别的事务提交修改;第二次 select 生成新 read‑view,可以读到别人提交后的新值 →出现不可重复读。

RC 级别:

  • 脏读 ❌消除;
  • 不可重复读 ✔存在;幻读 ✔存在
  • RC 下 InnoDB 关闭 Gap 锁(没有间隙锁),只有记录锁;当前读只加行记录锁,不加临键锁,会出现幻读。

3. REPEATABLE‑READ(RR,InnoDB 默认)可重复读

✅实现原理

Read‑View 只在事务内 第一次快照读(第一条普通 select)的时候生成一次;之后整个事务复用这同一个 Read‑View,不再重新生成!

👉 所以同一个事务多次普通 select,始终拿这一套视图,读到同一套快照,消除了【不可重复读】(快照读层面)。

⚠重点:RR 隔离级别快照读 MVCC 不能解决幻读!

MVCC 只是读旧快照;别的事务依然可以真实插入新行,只是你快照看不到。

RR 消除幻读靠【当前读】时的临键锁 (Next‑Key Lock = 记录锁 + Gap 间隙锁)

  • 当前读 (for update/update/delete) 时,会加 Next‑Key 临键锁锁住区间,阻止别的事务往这个区间插入新记录,从而防止幻读。

总结 RR:

  • 脏读❌;不可重复读❌(快照读 MVCC)
  • 幻读:快照读依然可以发生(只是看不见);当前读依靠临键锁防止幻读

RR 才开启 Gap 间隙锁;RC 没有间隙锁!这是 RC/RR 锁层面最大差异。

4. SERIALIZABLE 串行化

✅实现原理

不使用 MVCC 快照读了!

  • InnoDB 把所有普通的快照读 select,隐式自动转成SELECT ... LOCK IN SHARE MODE(加共享行锁)
  • 所有读操作都是当前读,读写互相阻塞;事务只能串行执行,完全隔离所有并发异常。
  • 并发性能最差,生产极少使用。
  • 脏读❌、不可重复读❌、幻读❌全部消除。

RC 与 RR 核心对比

项目RC (读已提交)RR (可重复读 默认)
Read‑View 生成时机每次 select 都新建事务第一次 select 生成,复用到底
MVCC 解决不可重复读❌✅快照读解决
间隙锁 Gap 锁关闭,只有记录锁开启 Next‑Key 临键锁 (记录 + 间隙)
幻读 (当前读)存在幻读临键锁阻止幻读

补充面试易错点

1.❌错误认知:RR 隔离级别 MVCC 直接解决幻读

✅正确:MVCC 只是快照;MVCC 本身不能阻止其他事务物理插入,只有临键锁(当前读)才阻止幻读。

2.RC 没有间隙锁,所以 update 范围查询的时候别的事务可以插入,发生幻读。

3.MVCC 只服务快照读;所有 DML (update/delete/for update) 永远都是当前读,不走快照。

一句话速记版

  • RU:无 MVCC,直接读最新,脏读;
  • RC:每次 select 生成 read‑view,消除脏读;无间隙锁,不可重复读、幻读存在;
  • RR:事务首次 select 生成 read‑view 复用,MVCC 消除快照读不可重复读;开启临键锁,锁住区间防止当前读幻读;
  • SERIALIZABLE:全部 select 隐式加共享锁,放弃快照,串行执行。



InnoDB 隔离级别:MVCC + 锁 分工

核心结论:

MVCC 负责「快照读 (普通 select)」;锁(行锁、Gap 间隙锁、Next‑Key 临键锁)负责「当前读 (for update /update/delete)」。两者各司其职,一起实现 4 种隔离级别。

1、先分清两类读(最重要)

① 快照读(Snapshot Read)

普通select * from t;

  • ✅走MVCC,读 undo log 历史快照,不加锁
  • MVCC 组件:隐藏列 DB_TRX_ID、DB_ROLL_PTR、undo log 版本链、Read‑View
  • MVCC 只能解决读的可见性问题(脏读、不可重复读)
  • MVCC无法物理阻止别的事务修改、插入数据;只是自己读到旧快照看不见新数据而已!

② 当前读(Current Read)

select ... for update select ... lock in share mode update / delete / insert
  • ✅走锁机制(行锁 / Gap 间隙锁 / Next‑Key 临键锁)
  • 读取磁盘上最新的数据;靠锁物理阻塞其它事务的写、插入操作
  • 用来解决幻读的是锁,不是 MVCC

2、四种隔离级别下 MVCC 与锁的开启情况

1. READ‑UNCOMMITTED 读未提交

  • ❌不使用 MVCC、不生成 Read‑View;直接读取最新的数据行
  • 锁:普通 select 不加锁;DML 依旧行锁;会出现脏读

2. READ‑COMMITTED RC 读已提交

  • ✅使用 MVCC;每一次快照读都新建 Read‑View
  • 🔒锁层面:关闭 Gap 间隙锁,只有记录锁(行锁)
  • 现象:快照读会出现不可重复读;当前读范围查询会幻读(没有间隙锁挡插入)

3. REPEATABLE‑READ RR(MySQL 默认)

  • ✅使用 MVCC;事务第一次快照读生成 Read‑View,整个事务复用这一份

    MVCC:快照读层面消除不可重复读;但是仅仅是读旧快照!别的事务仍然可以物理插入新行。

  • 🔒锁层面:开启 Next‑Key Lock(临键锁 = 记录锁 + Gap 间隙锁)

    当前读的时候,临键锁锁住整个区间,物理阻止别的事务往区间插入新记录,从而解决当前读的幻读

⚠易错:快照读依旧 “伪幻读”:别的事务已经插进去了,只是 MVCC 快照看不到。

3. SERIALIZABLE 串行化

  • ❌关闭 MVCC 快照读;普通 select 隐式变成lock in share mode
  • 🔒全部变成当前读,加共享锁;读写互相阻塞,事务串行跑。

3、一句话区分分工

MVCC 解决快照读的可见性;锁解决当前读的并发写入阻塞。

RC/RR 的 Read‑View 时机控制 MVCC 可见性;RR 独有的临键锁负责物理阻挡插入,解决当前读幻读。

4、误区

1.❌错误:RR 隔离级别依靠 MVCC 解决幻读

✅正确:MVCC 只管读快照,拦不住别人插入;RR 是依靠临键锁 (Next‑Key Lock) 在当前读时防止幻读。

2.RC 级别没有间隙锁!只有 RR 才开启间隙锁。

3.MVCC 只作用于快照读;所有 DML 永远是当前读,不走 undo 快照版本链。




先把三个问题梳理

1)RC (读已提交) 为什么还要加行锁?

2)RR (可重复读) 为什么要加临键锁 Next‑Key Lock?

3)RR 到底有没有解决幻读?(重点易错)

1、RC 读已提交为什么加行锁?

⚠️记住:RC 只是关闭了 Gap 间隙锁,但是【记录锁(行锁)依然存在】

MVCC 仅仅作用于快照读(普通 select 不加锁)

update / delete / select … for update属于当前读,和 MVCC 无关!

RC 的锁策略:

  • ✅快照读:MVCC,不加锁
  • ✅当前读:只加记录锁(行锁),不加间隙锁

为什么 RC 还需要行锁?

行锁的作用是防止同一条记录被多个事务同时修改,解决写‑写冲突。

举例:事务 A update id=5;事务 B 也 update id=5;行锁拦住 B,避免两条事务同时改写同一行,丢失更新。

RC 只是不去锁间隙;也就是:同一行不能并发改,但是间隙之间允许别的事务插入新行。

👉所以 RC 范围的当前读就会出现幻读:锁住已经存在的行,但没锁住空隙,别人可以往空隙插入新记录。


2、RR 可重复读为什么引入临键锁 Next‑Key Lock(记录锁 + Gap 间隙锁)

临键锁 = 记录锁 + 间隙锁。

RR 的快照读靠 MVCC 解决不可重复读;但是 MVCC 拦不住别的事务 INSERT!

MVCC 只是读旧版本快照,做不到物理阻止其他事务在查询的区间插入新数据。

当执行范围的当前读,例如:

select * from t where id > 10 for update;

如果只加普通行锁,只能锁住已经存在的那些行;id>10 的空隙没有锁,别的事务可以插入 id=11,12。 事务 A 再次做这条for update当前读,就会读到刚刚新插入的 id=11,发生幻读。

👉所以 InnoDB 在 RR 隔离级别,范围当前读时使用临键锁,把整个查询的区间全部锁住(包括中间空隙 Gap),禁止别的事务往区间里面 INSERT 新行。

目的:在当前读层面物理阻止幻读的发生。

补充小知识点:RR 也不是所有情况都一定生成临键锁;如果是等值查询,命中唯一索引精确匹配一行,会降级退化成普通记录锁。


3、RR 到底有没有解决幻读?(这道题坑最多)

幻读定义:同一个事务内,前后两次相同查询,第二次返回多出之前没有的行。

分两种读场景:

1. 快照读(普通 select,MVCC):RR 没有消除幻读(伪幻读)

事务 A 全程普通 select,复用同一份 read‑view;即使 B 事务已经物理插入提交了新行,A 读到的还是旧快照,看不见新增的数据。

⚠注意:数据库里面数据其实已经插进去了,只是 MVCC 快照看不到。

不是阻止了插入,只是看不见,这叫伪幻读。

2. 当前读(update /delete/for update):RR 依靠临键锁,物理锁住区间,禁止插入,解决了当前读场景下的幻读 。

✅最终标准答案

InnoDB 的 RR 隔离级别:

  • 对于快照读(普通 select):依靠 MVCC,并没有真正解决幻读,只是看不到新插入的数据;
  • 对于当前读(DML、for update 等):依靠临键锁 (Next‑Key Lock) 锁住查询区间,物理阻塞插入,解决幻读;所以不能笼统说 RR 完全解决幻读,也不能笼统说 RR 完全没解决幻读,要看是快照读还是当前读。

一句话总结

  1. RC 保留行 (记录) 锁是为了解写写冲突防止丢失更新;去掉间隙锁,允许间隙插入,并发更好。
  2. RR 增加临键锁(记录锁 + Gap),专门用来对付范围当前读时别的事务插入新行造成的幻读。
  3. RR 幻读分快照读和当前读两套结果,是 MVCC 和锁两套机制分别生效带来的现象。

拓展:SERIALIZABLE 隔离级别,全部读变成当前读加锁,所以不管快照读、当前读幻读全部消除。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询