隔离级别总览表
| 隔离级别 | 脏读 (Dirty Read) | 不可重复读 (Non-Repeatable Read) | 幻读 (Phantom Read) | 普通 SELECT 是否加锁 | Read View 创建时机 | 并发性能 | MySQL 默认? | 主要实现机制 |
|---|---|---|---|---|---|---|---|---|
| READ UNCOMMITTED | 允许 | 允许 | 允许 | 不加锁 | 不使用 Read View | 最高 | 否 | 直接读最新数据(无 MVCC) |
| READ COMMITTED (RC) | 不允许 | 允许 | 允许 | 不加锁 | 每次 SELECT 都新建 Read View | 高 | 否 | MVCC + 每次刷新快照 |
| REPEATABLE READ (RR) | 不允许 | 不允许 | MySQL 中基本不允许 | 不加锁(一致性读) | 事务内第一次 SELECT 时创建一次,全程复用 | 中等偏高 | 是(默认) | MVCC + Next-Key Lock(间隙锁) |
| SERIALIZABLE | 不允许 | 不允许 | 彻底不允许 | 加锁(隐式共享锁) | 同 RR(第一次 SELECT 创建) | 最低 | 否 | MVCC + 共享锁 + 范围间隙锁 |
脏读、不可重复读、幻读?
- 脏读(Dirty Read)定义:
一个事务读取到了另一个事务尚未提交(未 COMMIT) 的数据。例子:
- 事务 A:把账户余额从 1000 修改为 1500,但还没有 COMMIT。
- 事务 B:此时去查询该账户余额,看到的是 1500。
- 之后事务 A 执行了 ROLLBACK(回滚),余额又变回 1000。
- 结果:事务 B 读到的 1500 是脏数据(从来没真正存在过)。
后果:非常危险!可能导致业务逻辑错误(如多发货、重复扣款等)。哪个隔离级别允许脏读?
只有 READ UNCOMMITTED 允许脏读。其他三个隔离级别(RC、RR、SERIALIZABLE)都不允许脏读。
- 不可重复读(Non-Repeatable Read)定义:
同一个事务内,两次读取同一行数据,结果却不一样。例子:
-
事务 A 开始:
- 第一次查询:账户余额 = 1000
-
这时事务 B 把余额修改为 1500 并 COMMIT 了
-
事务 A 第二次查询同一行:余额变成了 1500
关键特点:
- 针对的是同一行数据被修改(UPDATE/DELETE)。
- 读到的数据是别人已经提交的(不是脏读)。
哪个隔离级别会发生?
- READ COMMITTED:允许不可重复读
- REPEATABLE READ:不允许(MySQL 默认级别已解决)
- SERIALIZABLE:也不允许
- 幻读(Phantom Read)定义:
同一个事务内,两次执行相同的范围查询,第二次查询却多出来(或少掉)一些行,像出现了“幻影”一样。例子:
-
事务 A 开始:
- 查询 SELECT * FROM orders WHERE user_id = 1; → 返回 5 条记录
-
这时事务 B 插入了一条 user_id = 1 的新订单,并 COMMIT
-
事务 A 再次执行完全相同的查询:→ 返回 6 条记录
虽然每行数据本身没变,但结果集的行数发生了变化,这就是幻读。幻读 vs 不可重复读 的区别:
- 不可重复读:同一行数据前后不一致(UPDATE/DELETE)
- 幻读:范围查询的结果集前后不一致(主要是 INSERT)
哪个隔离级别会发生?
- READ COMMITTED:允许幻读
- REPEATABLE READ:MySQL 通过 Next-Key Lock(间隙锁),很大程度上防止了幻读(不是 100% 杜绝所有情况)
- SERIALIZABLE:彻底防止幻读(会加范围锁)
总结对比表(最清晰版)
| 含义 | 针对的对象 | READ UNCOMMITTED | READ COMMITTED | REPEATABLE READ(默认) | SERIALIZABLE | |
|---|---|---|---|---|---|---|
| 脏读 | 读到别人还没提交的数据 | 任何数据 | 允许 | 不允许 | 不允许 | 不允许 |
| 不可重复读 | 同一行数据,两次读结果不同 | 同一行(UPDATE) | 允许 | 允许 | 不允许 | 不允许 |
| 幻读 | 范围查询结果集,两次查询行数不同 | 范围(INSERT) | 允许 | 允许 | 基本不允许 | 不允许 |
为什么 READ COMMITTED 就能解决脏读?
-
在这个级别下,InnoDB 会保证普通 SELECT 只读取其他事务已提交的版本。
-
未提交的修改(即使别人正在改)对你来说是不可见的。
-
它通过 MVCC(多版本并发控制) + Read View 机制实现。
RR 如何解决不可重复读?
核心机制:MVCC + Read View(一致性快照)具体实现方式如下:
-
Read View 只创建一次
-
当事务开始后,执行第一次普通 SELECT(快照读)时,MySQL 会创建一个 Read View(读视图/一致性快照)。
-
这个 Read View 会记录:
- 当前活跃(未提交)事务的 ID 列表(m_ids)
- 最小活跃事务 ID(up_limit_id / min_trx_id)
- 下一个事务 ID(low_limit_id / max_trx_id)
- 当前事务自己的 ID
-
-
整个事务期间复用同一个 Read View
- 后续的所有普通 SELECT(不加 FOR UPDATE 的查询),都使用同一个 Read View 来判断数据可见性。
- 即使中间有其他事务修改了数据并提交了,当前事务也始终按照第一次创建 Read View 时的快照来读取数据。
-
通过版本链查找可见版本
- 每行数据都有隐藏的 trx_id(事务 ID)和 roll_ptr(指向 Undo Log 的指针),形成版本链。
- 查询时从最新版本开始,沿着版本链往下找,判断哪个版本对当前 Read View 是可见的。
- 因为 Read View 不变,所以每次找到的可见版本都是同一个 → 结果一致。
SERIALIZABLE 怎么解决幻读?
核心机制:MVCC + 隐式加锁(尤其是范围锁 / 间隙锁)具体做法是:
-
依然使用 MVCC + Read View(和 RR 一样的基础)
- 普通 SELECT 仍然走一致性读(快照读)。
-
最关键的加强措施 —— 隐式加共享锁(Shared Lock)
- 在 SERIALIZABLE 隔离级别下,InnoDB 会自动把所有普通 SELECT 语句隐式转换为 SELECT ... FOR SHARE(共享锁,旧版本叫 LOCK IN SHARE MODE)。
- 这意味着:即使你写的是最普通的 SELECT,MySQL 也会给你查询到的行加上共享锁(S 锁)。
-
范围锁(Gap Lock + Next-Key Lock)
- 当你执行范围查询(带 WHERE 条件)时,InnoDB 不仅锁住已存在的行,还会锁住查询范围内的间隙(Gap)。
- 其他事务无法在被锁定的间隙中插入新行,也无法修改或删除已锁定的行。
- 这就彻底阻止了“新行插入”导致的幻读。
结果:其他事务想插入、修改、删除影响当前事务查询结果的数据时,都会被阻塞(等待当前事务 COMMIT 或 ROLLBACK),从而让并发事务看起来像一个接一个串行执行一样。实际例子对比场景:
- 事务 A:SELECT * FROM orders WHERE user_id = 1;(查询用户 1 的订单,第一次返回 5 条)
- 事务 B:尝试插入一条 user_id = 1 的新订单
在 REPEATABLE READ 下:
- 事务 A 的查询通常能防止大部分幻读,但某些复杂条件下仍可能出现新行插入(幻读)。
在 SERIALIZABLE 下:
- 事务 A 执行 SELECT 时,自动加上共享锁 + 范围间隙锁。
- 事务 B 的 INSERT 操作会被阻塞(等待事务 A 结束)。
- 事务 A 第二次查询时,仍然看到 5 条记录(没有幻影出现)。
- 只有事务 A COMMIT 或 ROLLBACK 后,事务 B 的 INSERT 才能执行。
SERIALIZABLE 的优缺点优点:
- 彻底解决脏读、不可重复读、幻读(隔离性最强)。
- 事务最终效果等同于串行执行,数据一致性最高。
缺点(非常明显):
- 并发性能最差 —— 读也加锁,大量锁等待和死锁风险。
- 容易导致性能瓶颈,不适合高并发系统。
- 只在对一致性要求极高、并发量很低的场景使用(如金融核心系统、XA 分布式事务等)。
如何设置 SERIALIZABLE
sql
-- 当前事务设置
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
-- 当前会话设置
SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE;
-- 查看当前隔离级别
SELECT @@transaction_isolation;
生产建议:
- 大多数业务用 REPEATABLE READ(默认)就足够了。
- 只有当业务明确要求完全杜绝幻读且能接受性能损失时,才使用 SERIALIZABLE。
一句话总结:
SERIALIZABLE 通过 MVCC(快照读) + 隐式把普通 SELECT 转为加共享锁 + 范围间隙锁,彻底锁住查询范围,阻止其他事务插入/修改影响结果的数据,从而完全解决幻读。