MySQL 事务隔离级别详解
关键词:隔离级别、脏读、不可重复读、幻读、MVCC、间隙锁。
重点:搞清楚四个隔离级别分别“允许/禁止”哪些现象,以及 InnoDB 实际是怎么做的。
一、为什么需要事务隔离?
在数据库里,多个事务是并发执行的:
- 一个事务在读数据;
- 另一个事务在改数据;
- 还有一个事务在插入/删除数据。
如果完全不隔离:
- 你可能读到别人还没提交的修改(脏读);
- 同一个事务里两次读同一行,结果却不同(不可重复读);
- 同一个事务里两次按条件查询,前后出现的行数不一样(幻读)。
事务隔离级别就是在:
“读到最新数据” 和 “读到稳定一致的数据” 以及 “系统性能” 之间做平衡。
SQL 标准定义了 4 个隔离级别,对应不同的安全性和性能取舍。
二、三种典型并发现象
先搞清楚几个常见“坏现象”是什么。
2.1 脏读(Dirty Read)
- 事务 A 读取到了事务 B 尚未提交 的修改;
- 如果 B 后面回滚了,A 读到的就是“根本没生效过的假数据”。
示意:
T1: 事务 B:UPDATE account SET balance = balance - 100 WHERE id = 1;
T2: 事务 A:SELECT balance FROM account WHERE id = 1; -- 读到了减少 100 后的值
T3: 事务 B:ROLLBACK;
此时事务 A 读到的就是“脏数据”。
2.2 不可重复读(Non-Repeatable Read)
- 同一个事务中,两次读取同一行数据,结果不一样;
- 原因是:两次读取之间,别的事务提交了对该行的修改。
示意:
T1: 事务 A:SELECT balance FROM account WHERE id = 1; -- 读到 1000
T2: 事务 B:UPDATE account SET balance = 800 WHERE id = 1; COMMIT;
T3: 事务 A:再次 SELECT balance FROM account WHERE id = 1; -- 读到 800
对于一部分业务来说,“同一个事务里两次查应该一致”是刚性需求。
2.3 幻读(Phantom Read)
- 同一个事务中,两次按某个条件查询,结果的行数不一样;
- 原因是:两次查询之间,有别的事务插入/删除了满足条件的行。
示意:
T1: 事务 A:SELECT * FROM order WHERE amount > 100; -- 结果 5 行
T2: 事务 B:INSERT INTO order(id, amount) VALUES(100, 200); COMMIT;
T3: 事务 A:再次 SELECT * FROM order WHERE amount > 100; -- 结果 6 行
对于“统计 + 校验”等场景,这种“幻影数据”可能会搞乱逻辑。
三、四种事务隔离级别(SQL 标准)
SQL 标准定义了 4 个隔离级别:
- READ UNCOMMITTED(读未提交)
- READ COMMITTED(读已提交)
- REPEATABLE READ(可重复读)
- SERIALIZABLE(可串行化)
它们从“弱到强”的对应关系是:
READ UNCOMMITTED < READ COMMITTED < REPEATABLE READ < SERIALIZABLE
隔离越强:
- 读到的数据越“稳定一致”;
- 并发性能通常越差。
3.1 READ UNCOMMITTED(读未提交)
特点:
- 可以读到别的事务尚未提交的修改;
- 性能最高,但数据安全性最差。
存在问题:
- ✅ 脏读:可能;
- ✅ 不可重复读:可能;
- ✅ 幻读:可能。
这个隔离级别一般不会在生产中使用。
3.2 READ COMMITTED(读已提交)
特点:
- 每一次查询都只能看到已经提交的最新数据;
- 每次 SELECT 都会生成一个新的“视图”。
效果:
- ❌ 脏读:被禁止;
- ✅ 不可重复读:依然可能;
- ✅ 幻读:依然可能。
示意:
事务 A:
SELECT balance FROM account WHERE id = 1; -- 第一次读
事务 B:
UPDATE account SET balance = 800 WHERE id = 1; COMMIT;
事务 A:
SELECT balance FROM account WHERE id = 1; -- 第二次读,看到 800
两次读到的结果可能不同,因此称为“读已提交”,而不是“可重复读”。
在很多商业数据库(如 Oracle)中,默认隔离级别就是 READ COMMITTED。
3.3 REPEATABLE READ(可重复读)
特点:
- 一个事务从开始到结束,看到的数据是一致的“快照”;
- 同一事务中多次读同一行,结果保持一致;
- MySQL InnoDB 的默认隔离级别。
效果:
- ❌ 脏读:禁止;
- ❌ 不可重复读:禁止(对同一行);
- ✅ 幻读:按照 SQL 标准定义,理论上仍可能。
但是:
InnoDB 在 REPEATABLE READ 下,配合 MVCC + 间隙锁/Next-Key Lock,在大多数场景下也能避免幻读;
只是在严格定义上,仍认为“幻读可能存在某些边界情况”。
示意:
事务 A(RR):
第一次 SELECT * FROM t WHERE id = 1; -- 读到旧版本
事务 B:
UPDATE t SET ... WHERE id = 1; COMMIT; -- 提交新值
事务 A:
第二次 SELECT * FROM t WHERE id = 1; -- 仍然读到第一次看到的版本
因为 A 的“视图”是在事务开始时创建的,后续读都以这个视图为准。
3.4 SERIALIZABLE(可串行化)
特点:
- 最高隔离级别;
- 强制所有事务看起来像是顺序执行的;
- 通常通过对读也加锁(类似范围锁/表锁)来实现。
效果:
- ❌ 脏读:禁止;
- ❌ 不可重复读:禁止;
- ❌ 幻读:禁止。
代价是:
- 并发能力大幅下降;
- 在高并发场景下一般不建议使用。
四、MySQL InnoDB 中的实际实现细节
4.1 全局 / 会话隔离级别
查看当前隔离级别(MySQL 8.0):
SELECT @@transaction_isolation;
-- 或
SELECT @@global.transaction_isolation;
设置隔离级别:
-- 设置全局隔离级别
SET GLOBAL transaction_isolation = 'REPEATABLE-READ';
-- 设置当前会话隔离级别
SET SESSION transaction_isolation = 'READ-COMMITTED';
MySQL 5.x 常用写法(已逐渐被替换):
SELECT @@tx_isolation;
SET SESSION tx_isolation = 'READ-COMMITTED';
InnoDB 默认隔离级别:REPEATABLE READ。
4.2 READ COMMITTED vs REPEATABLE READ:差别在哪?
核心差别在于一致性视图(Read View)创建时机:
-
READ COMMITTED:
- 每次 SELECT 都会生成一个新的 Read View;
- 因此每次查询都能看到当前已经提交的最新版本;
- 故容易出现“不可重复读”。
-
REPEATABLE READ:
- 在事务第一次读取数据时创建 Read View;
- 整个事务期间都复用同一个 Read View;
- 后续再读同一行,看到的仍然是第一次看到的版本;
- 避免了不可重复读。
这就是 InnoDB 利用 MVCC(多版本并发控制) 做出不同隔离语义的核心。
4.3 幻读与间隙锁 / Next-Key Lock
为了控制“幻读”,InnoDB 在可重复读级别下:
- 对范围查询 + 锁定读(
SELECT ... FOR UPDATE / FOR SHARE)会加:- 间隙锁(Gap Lock):锁定某个区间,禁止在区间内插入新行;
- Next-Key Lock:记录锁 + 间隙锁的组合,锁
[前一个值, 当前值]区间。
这样:
- 一个事务在某个范围上进行锁定读或修改;
- 另一个事务就没法往这个范围内插入新记录;
- 从而在多数情况下避免了“我查询某条件范围,后面新插入的记录突然冒出来”的幻读问题。
不过:
- 对纯查询(普通 SELECT 不加锁)来说,避免幻读主要是通过 MVCC 来实现“读到一个旧快照”;
- 对加锁读来说,则通过 Next-Key Lock + Gap Lock 尽量避免幻读。
五、各隔离级别与并发现象对照表
用一个表总览一下:
| 隔离级别 | 脏读(Dirty Read) | 不可重复读(Non-Repeatable) | 幻读(Phantom) |
|---|---|---|---|
| READ UNCOMMITTED | ✅ 可能 | ✅ 可能 | ✅ 可能 |
| READ COMMITTED | ❌ 禁止 | ✅ 可能 | ✅ 可能 |
| REPEATABLE READ* | ❌ 禁止 | ❌ 禁止 | ✅ 理论上可能* |
| SERIALIZABLE | ❌ 禁止 | ❌ 禁止 | ❌ 禁止 |
说明:
- 标准语义下,REPEATABLE READ 仍可能有幻读。
- InnoDB 在 REPEATABLE READ + 间隙锁/Next-Key Lock 下,大部分实际场景中也能避免幻读。
六、怎么选隔离级别?(实践建议)
-
绝大多数业务:用默认的 REPEATABLE READ 就够了
- InnoDB 默认是 REPEATABLE READ;
- 配合 MVCC 和 Next-Key Lock,能很好平衡一致性和性能。
-
如果有强烈的“读最新”需求,可以考虑 READ COMMITTED
例如:
- 某些业务要求:一旦事务 B 提交,我的后续读立刻能看到它;
- 不介意“同一事务内两次读同一行不一样”。
-
SERIALIZABLE 一般只在一致性要求极端高且并发不大的系统里使用
- 比如一些对账、结算、强一致金融场景;
- 否则容易把并发性能打爆。
-
READ UNCOMMITTED 几乎没有场景需要,基本不要用
七、面试/复习高频问答小抄
Q1:MySQL 默认的事务隔离级别是什么?
- InnoDB 默认是 REPEATABLE READ(可重复读)。
Q2:四个隔离级别分别解决/遗留了哪些问题?
- READ UNCOMMITTED:什么问题都不解决,允许脏读;
- READ COMMITTED:解决脏读;不可重复读、幻读仍存在;
- REPEATABLE READ:解决脏读 + 不可重复读;幻读理论上仍可能;
- SERIALIZABLE:理论上解决所有问题,但性能最差。
Q3:READ COMMITTED 和 REPEATABLE READ 的实现差别?
- READ COMMITTED:每次 SELECT 都新建一个 Read View;
- REPEATABLE READ:事务第一次 SELECT 时创建 Read View,后续复用;
- 都依赖 MVCC(多版本并发控制)。
Q4:InnoDB 如何处理幻读?
- 利用 MVCC 提供一致性视图,使得查询看到的是历史快照;
- 配合 间隙锁(Gap Lock)、Next-Key Lock,在加锁读和更新场景中尽量避免其他事务在范围内插入新行。
Q5:如何查看和设置当前隔离级别?
-- 查看
SELECT @@transaction_isolation;
-- 设置当前会话
SET SESSION transaction_isolation = 'READ-COMMITTED';
八、小结
- 事务隔离级别是用来控制并发读写时“看见什么”的规则集合;
- MySQL/InnoDB 提供四个级别:读未提交、读已提交、可重复读、可串行化;
- 实现手段主要有:
- MVCC(多版本并发控制):解决大部分读写并发问题;
- 行锁 + 间隙锁 + Next-Key Lock:控制并发写和“范围插入”;
- 日常绝大部分情况:
- 用 InnoDB 默认的 REPEATABLE READ 就足够;
- 极端需要读最新且能接受不可重复读时,用 READ COMMITTED。
理解了“隔离级别 = 看见哪些版本的数据 + 加哪些锁”,你基本就把 MySQL 事务隔离级别的本质吃透了。
2173

被折叠的 条评论
为什么被折叠?



