
你好,我是《Redis 高手心法》的作者码哥。后端架构师,InfoQ 签约作者、腾讯云架构联盟成员。擅长用风趣直白的语言把复杂技术讲得清清楚楚,坚持“拥抱技术与对象,面向人民币编程”的宗旨。擅长手拿菜刀砍电线,一路火花带闪电。
作为一名长期与 MySQL 打交道的技术人,今天我们将深入数据库系统的核心——事务隔离级别。
这不仅是面试中的高频考点,更是构建稳定、一致高性能应用的基石。
很多人对这四个级别(读未提交、读已提交、可重复读、串行化)仅停留在概念层面,但理解其背后的“为何”与“如何”,才能真正让你在设计和排查问题时游刃有余。
本文将不仅详细解释每种隔离级别的定义和现象,更会深入 InnoDB 存储引擎层,探讨其背后的多版本并发控制(MVCC)和锁机制是如何协同工作来实现这些隔离级别的。
在深入隔离级别之前,我们必须先统一共识:什么是事务?为什么需要它?
事务是数据库操作的一个最小逻辑工作单元,其内的所有操作要么全部成功,要么全部失败。一个经典的例子就是银行转账:从 A 账户扣款和向 B 账户加款,这两个操作必须作为一个整体,不能分割。
为了确保事务的可靠性和数据的一致性,数据库系统必须满足 ACID 属性:
隔离性的挑战: 完全隔离(串行化执行)固然安全,但会极大限制数据库的并发处理能力,导致性能急剧下降。因此,数据库系统提供了多种隔离级别,让开发者可以在性能和数据一致性之间进行权衡。
在介绍隔离级别之前,我们必须先了解如果不进行任何隔离控制,并发事务会引发哪些问题。隔离级别本质上就是为了解决这些问题而存在的。
一个事务读到了另一个未提交事务修改的数据。如果另一个事务中途回滚,那么第一个事务读到的数据就是“脏”的、无效的。
示例:

一个事务内,两次读取同一个数据项,得到了不同的结果。重点在于另一个已提交事务对数据进行了修改(UPDATE)。
示例:

一个事务内,两次执行同一个查询,返回的结果集行数不同。重点在于另一个已提交事务对数据进行了增删(INSERT/DELETE),像产生了幻觉一样。
示例:

不可重复读 vs 幻读:
为了解决上述问题,SQL 标准定义了四种隔离级别,严格程度从低到高。级别越高,能解决的问题越多,但并发性能通常越低。
隔离级别 | 脏读 | 不可重复读 | 幻读 |
|---|---|---|---|
读未提交 (Read Uncommitted) | ❌ 可能 | ❌ 可能 | ❌ 可能 |
读已提交 (Read Committed) | ✅ 避免 | ❌ 可能 | ❌ 可能 |
可重复读 (Repeatable Read) | ✅ 避免 | ✅ 避免 | ❌ 可能 |
串行化 (Serializable) | ✅ 避免 | ✅ 避免 | ✅ 避免 |
注意: 在 MySQL 的 InnoDB 引擎中,通过 Next-Key Locking 技术,在可重复读(Repeatable Read) 隔离级别下就已经可以避免绝大部分的幻读现象。这是 MySQL 对标准隔离级别的增强,也是其默认使用该级别的重要原因。
MySQL 的服务层负责事务管理,确保在执行一系列操作时,满足原子性、一致性、隔离性和持久性这四个特性。事务管理涉及的主要功能包括:
要理解不同隔离级别是如何实现的,我们必须揭开 InnoDB 的两大核心法宝:多版本并发控制 (MVCC) 和 锁 (Locking) 。
在这里先介绍下我最新的专栏 -》《互联网大厂面试高手心法 58 讲》,原价 58元,早鸟价 10 元,现在已经涨价到 20元,2025-9-2 号 立马涨价,不要再错过。


MVCC 的核心思想是为每一行数据维护多个版本(通常是两个),通过某个时间点的“快照”(Snapshot)来读取数据,从而避免加锁带来的性能损耗,实现非阻塞的读操作。
InnoDB 为每行记录隐式地添加了三个字段:
DB_TRX_ID(6 字节): 最近一次修改该行数据的事务 ID。DB_ROLL_PTR(7 字节): 回滚指针,指向该行数据在 Undo Log 中的上一个历史版本。DB_ROW_ID(6 字节): 行标识(隐藏的主键,如果表没有主键)。关键概念:ReadView在 MVCC 中,事务在执行快照读(普通的SELECT语句)时会生成一个一致性视图,即ReadView。ReadView 是 InnoDB 实现 MVCC 的核心数据结构,它包含:
m_ids:生成 ReadView 时活跃的事务 ID 列表min_trx_id:活跃事务中的最小 IDmax_trx_id:下一个将被分配的事务 IDcreator_trx_id:创建该 ReadView 的事务 ID通过 ReadView,InnoDB 可以判断某个数据版本对当前事务是否可见。
数据可见性规则: 当一行数据被访问时,InnoDB 会遍历其版本链(通过DB_ROLL_PTR指针),并应用以下规则来判断哪个版本对当前事务是可见的:
trx_id < min_trx_id,说明该版本在 ReadView 创建前已提交,可见。trx_id >= max_trx_id,说明该版本在 ReadView 创建后才开启,不可见。min_trx_id <= trx_id < max_trx_id,则需判断trx_id是否在m_ids中:creator_trx_id,说明是本事务自己修改的,可见。
MVCC 主要解决了“读”的并发问题,而“写”(INSERT, UPDATE, DELETE)依然需要通过加锁来保证数据正确性。
此外,InnoDB 还引入了意向锁(Intention Locks),是一种表级锁,用来表示事务稍后会对表中的某一行加上哪种类型的锁(共享或排他)。目的是为了更高效地判断表级锁冲突。

现在我们结合 MVCC 和锁,来看看每个隔离级别在 InnoDB 中是如何具体工作的。
在读未提交级别下,InnoDB 几乎不进行隔离控制,事务可以直接读取数据页上的最新值,无论其他事务是否已提交。
实现特点:
在读已提交级别下,InnoDB 使用 MVCC 确保事务只能读取已提交的数据。
实现特点:
RC 级别下的 MVCC 示例: 假设初始值 balance = 100。
时间序列 | 事务 A (trx_id=10) | 事务 B (trx_id=20) | 事务 A 的 ReadView 与读取结果 |
|---|---|---|---|
T1 | BEGIN; | BEGIN; | - |
T2 | UPDATE account SET balance = 200 WHERE id=1; | - | |
T3 | SELECT balance FROM account WHERE id=1; (生成 ReadView1: m_ids=[10,20], min=10, max=21) 检查数据行 trx_id=10,它在 m_ids 中且 ≠ 自己,不可见。沿 Undo Log 找到上一个版本(trx_id=可能为 null),读取到 100。 | ||
T4 | COMMIT; | - | |
T5 | SELECT balance FROM account WHERE id=1; (生成新的ReadView2: m_ids=[20], min=20, max=21) 检查数据行 trx_id=10,10 < 20 且不在 m_ids 中,可见。读取到 200。 |
在可重复读级别下,InnoDB 使用事务级快照确保事务内读取一致性。
实现特点:
RR 级别下的 MVCC 与 Next-Key Lock 示例:
MVCC 部分:
沿用上面的例子,在 T3 时刻事务 B 生成 ReadView 后,在 T5 时刻即使事务 A 提交了,事务 B依然使用旧的 ReadView1进行可见性判断,因此读到的依然是 100,实现了可重复读。
Next-Key Lock 防止幻读:
SELECT * FROM employees WHERE age = 25 FOR UPDATE; (当前读,加锁)INSERT INTO employees (age) VALUES (25); (被阻塞)Next-Key 锁是 InnoDB 在可重复读隔离级别下避免幻读的关键技术,它是记录锁(Record Lock)和间隙锁(Gap Lock)的组合。

这条FOR UPDATE语句会在 age=25 的索引记录上加行锁,并在其前后间隙加上间隙锁。事务 B 的插入操作如果 age=25 落在被锁住的间隙(例如插入一个 25),就会被阻塞,从而避免了幻读。

SELECT ... FOR SHARE的隐式版本)来实现。读写、写写冲突都会严重,导致大量事务阻塞,性能最差。理解了原理,我们最终要落地到实践。如何选择隔离级别?
1. MySQL 的默认级别:可重复读 (RR)InnoDB 选择 RR 作为默认级别,是因为其通过 MVCC 和 Next-Key Lock 在性能和一致性上取得了很好的平衡。在绝大多数应用场景下,它既能保证事务内数据的一致性(避免不可重复读和幻读),又提供了比串行化高得多的并发性能。
2. 读已提交 (RC) 的适用场景近年来,越来越多的应用选择将隔离级别设置为 RC。原因如下:
binlog_format=ROW的情况下,使用 RC 级别的主从复制也是安全的。选择 RR 还是 RC?
3. 如何设置和查看隔离级别
SELECT @@transaction_isolation;SET GLOBAL transaction_isolation = 'READ-COMMITTED';SET SESSION transaction_isolation = 'REPEATABLE-READ';4. 避免“长事务”无论选择哪种隔离级别,长事务都是数据库的大敌。在 RR 级别下,长事务会导致 Undo Log 版本链过长,占用大量存储空间,影响性能。同时,它持有的锁可能长时间不释放,阻塞其他事务。务必监控并优化掉长事务。
MySQL 的事务隔离级别是一个层次分明、权衡精妙的系统。从 RC 到 RR,不仅仅是隔离性的提升,更是 MVCC 从“每次生成视图”到“第一次生成视图”的转变,以及锁机制从“行锁”到“Next-Key Lock”的升级。
希望本文通过原理剖析和图示,能帮助你建立起对 MySQL 事务隔离级别深刻而直观的理解。
下次当你面对“幻读”、“不可重复读”这些问题时,或者当你需要为应用选择合适的隔离级别时,你的决策将会更加自信和准确。
记住,没有最好的隔离级别,只有最适合你业务场景的隔离级别。
最后介绍我人生的第一本书《Redis 高手心法》本书基于 Redis 7.0 版本,将复杂的概念与实际案例相结合,以简洁、诙谐、幽默的方式揭示了Redis的精髓。
本书完美契合你对一个具体技术学习的期望: Redis 核心原理、关键细节、应用场景以及如何取舍......
从 Redis 的第一人称视角出发,拟人故事化方式和诙谐幽默的言语与各路“神仙”对话,配合 158 张图,由浅入深循序渐进的讲解 Redis 的数据结构实现原理、开发技巧、运维技术和高阶使用,让人轻松愉快地学习。