[面试题] MySQL如何实现不同隔离级别?
📖 版块 A:知识讲解版 (温故知新)
MySQL(以 InnoDB 引擎为例)主要通过 MVCC(多版本并发控制) 和 锁机制 共同实现四种隔离级别:
-
Read Uncommitted (读未提交):
- 实现:几乎不加锁,直接读取内存中最新的记录。
- 问题:存在脏读。
-
Read Committed (读已提交):
- 实现:利用 MVCC。每次执行
SELECT语句时,都会重新创建一个新的 ReadView(一致性视图)。 - 效果:避免了脏读,但存在不可重复读。
- 实现:利用 MVCC。每次执行
-
Repeatable Read (可重复读 - 默认级别):
- 实现:利用 MVCC。仅在事务开启后的第一次
SELECT时创建一个 ReadView,后续查询共用同一个。 - 解决幻读:InnoDB 在 RR 级别下通过 Next-Key Locks(记录锁+间隙锁) 来锁定范围,防止其他事务插入新数据。
- 效果:避免了脏读、不可重复读,并在很大程度上解决了幻读。
- 实现:利用 MVCC。仅在事务开启后的第一次
-
Serializable (可串行化):
- 实现:通过 锁机制。所有的读操作都会隐式加 S 锁(共享锁),写操作加 X 锁(排他锁)。
- 效果:并发性能最低,强制事务排序执行。
MVCC 核心组件:
- Undo Log:保存数据的历史版本链。
- ReadView:决定当前事务能看到版本链中的哪个版本。包含四个核心字段:
m_ids(活跃事务ID列表)、min_trx_id、max_trx_id、creator_trx_id。
🎤 版块 B:面试实战版 (高手逐字稿)
Q:MySQL 如何实现不同隔离级别?
💬 高手逐字稿:
关于 MySQL 隔离级别的实现,最核心的机制就是 MVCC(多版本并发控制) 和 锁机制。
首先,对于“读未提交(RU)”,MySQL 直接读取版本链上最新的数据,不做任何限制,所以会有脏读。而“可串行化(Serializable)”则是通过全表加锁的方式强行让事务排队,性能最差。
我们重点聊聊“读已提交(RC)”和“可重复读(RR)”,它们都是基于 MVCC 实现的,其核心区别在于 ReadView(一致性视图)生成的时机不同。
在 RC(读已提交) 级别下,事务中每一条 SELECT 语句在执行前都会生成一个新的 ReadView。这意味着,如果另一个事务在两次查询之间提交了,由于 ReadView 是新的,当前事务就能看到最新的改动,这就导致了“不可重复读”。
而在 RR(可重复读) 级别下,事务只在执行第一条 SELECT 语句时生成一个 ReadView,整个事务过程中都复用这一个。即使其他事务提交了修改,根据 ReadView 的可见性规则(比如判断事务 ID 是否在活跃事务列表 m_ids 中),当前事务依然只能从 Undo Log 链中找回事务开始时的老版本数据,从而实现了“可重复读”。
这里有个细节容易被忽略:RR 级别是如何解决幻读的? 很多人认为 MVCC 解决了幻读,但其实 MVCC 只能解决“快照读”下的幻读。对于“当前读”(比如 SELECT ... FOR UPDATE),InnoDB 会使用 Next-Key Locks,也就是记录锁加上间隙锁(Gap Lock)。它会锁定记录之间的间隙,防止其他事务在这个范围内插入新数据,从而在底层彻底封堵了幻读的可能性。
总结来说,RC 和 RR 的本质区别就是 ReadView 的快照频率:RC 是语句级的快照,而 RR 是事务级的快照。再配合上间隙锁,RR 就成为了 InnoDB 兼顾性能与安全的默认选择。