事务隔离级别
隔离级别定义了并发事务之间,读操作能看到怎样的数据,以及引擎为正确性付出多少锁与版本维护成本。级别越高,异常现象越少,但并发能力与实现复杂度通常更差。
MySQL InnoDB 默认:REPEATABLE READ(可重复读)。
1. 三种经典读异常
在讲级别之前,先固定术语(以「事务 A 在读,事务 B 在写」为例):
| 现象 | 含义 |
|---|---|
| 脏读(Dirty Read) | A 读到 B 尚未提交的修改;B 若回滚,A 读到的是无效数据 |
| 不可重复读(Non-Repeatable Read) | A 两次读同一行,中间 B 提交了修改,A 两次结果不同 |
| 幻读(Phantom Read) | A 两次按同一条件范围查询,中间 B 提交了插入/删除,A 看到「多出行或少出行」(幻影行) |
对比记忆:
- 不可重复读侧重同一行内容变了。
- 幻读侧重行的集合变了(多/少行)。
2. 四级隔离与能防止的现象
SQL 标准中的四个级别:
| 隔离级别 | 脏读 | 不可重复读 | 幻读 | 备注 |
|---|---|---|---|---|
| READ UNCOMMITTED | 可能 | 可能 | 可能 | 几乎不用 |
| READ COMMITTED | 否 | 可能 | 可能 | PostgreSQL 默认;Oracle 风格常见 |
| REPEATABLE READ | 否 | 否* | 视实现 | InnoDB 默认;*见下文 InnoDB 增强 |
| SERIALIZABLE | 否 | 否 | 否 | 最强,并发最差 |
「否」表示标准要求该级别不得出现该现象;具体引擎可能用锁或用 MVCC 达到类似效果。
3. InnoDB 各级别要点
3.1 READ UNCOMMITTED
- 可能读到未提交数据。
- 仅适合对正确性极不敏感的场景,生产几乎不用。
3.2 READ COMMITTED(RC)
- 每次普通
SELECT看到的是当前已提交的最新版本(语句级一致性)。 - 同一事务内两次读同一行,中间若有别人提交更新,第二次可能不同 → 允许不可重复读。
- 行锁在语句结束等时机的行为与 RR 有差异;间隙锁使用更少,幻读/范围写入场景与 RR 不同。
- 很多互联网业务在 RC 下也能工作,但要清楚「同一事务内两次读可能不一致」。
3.3 REPEATABLE READ(RR,InnoDB 默认)
- 普通快照读:在事务中第一次读时建立读视图(Read View),之后同事务内的一致读看到同一快照,从而实现可重复读。
- InnoDB 在 RR 下通过 MVCC + 间隙锁/临键锁(在当前读时)显著缓解幻读;标准意义上的「幻读」在 InnoDB RR 中表现与教科书不完全一样,需区分快照读与当前读。
3.4 SERIALIZABLE
- 强制串行化调度观感:InnoDB 会把普通
SELECT也变成类似加锁读,并发大幅下降。 - 仅在正确性极端优先、负载可控时使用。
查看与设置(会话级示例):
SELECT @@transaction_isolation;
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
4. 快照读 vs 当前读(理解 InnoDB 的钥匙)
| 类型 | 典型 SQL | 行为 |
|---|---|---|
| 快照读 | 普通 SELECT ... | 读历史版本(MVCC),不加行锁(一般情况) |
| 当前读 | SELECT ... FOR UPDATE / LOCK IN SHARE MODE;UPDATE/DELETE 定位行 | 读最新已提交版本并加锁 |
因此:
- 「RR 下为什么我还看到幻影?」——先看你是不是 当前读,以及别的事务是否用特殊方式插入。
- 「为什么
SELECT没锁住别人的更新?」——普通SELECT往往是快照读,本来就不靠锁挡写。
更细的加锁规则见 锁机制。
5. MVCC 直观模型
InnoDB 行上维护版本信息(简化):
行的当前版本 ← undo 链上的旧版本 ← 更旧版本 ...
每个事务有自己的 Read View,决定「哪些版本对自己可见」:
- 自己写的未提交:自己可见。
- 其他事务未提交:不可见(RC/RR 下)。
- 其他事务在「视图建立之后」才提交的修改:对 RR 的快照读通常不可见。
写仍然要落到最新行并加锁,不能只靠 MVCC 解决写写冲突。
6. 如何选择隔离级别
| 场景倾向 | 建议 |
|---|---|
| 默认业务、希望同事务多次读取稳定 | 保持 InnoDB 默认 RR |
| 希望减少间隙锁、提高并发,能接受语句间读到新提交 | 评估 RC(需回归测试) |
| 报表长事务、只要某时刻一致快照 | RR 快照读或显式控制事务边界 |
| 金融核心强一致、可接受吞吐下降 | 业务层约束 + 必要时更高隔离/串行化关键路径 |
不要迷信「级别越高越好」:很多问题应通过更短的事务、正确的索引与锁范围、乐观锁版本号解决,而不是一上来 SERIALIZABLE。
7. 实验建议(本地测试库)
开两个会话,分别设置隔离级别,交叉执行:
- B 更新未提交,A 能否读到(脏读)。
- A 读一行 → B 提交更新 → A 再读(不可重复读)。
- A 按条件计数 → B 插入符合条件的行并提交 → A 再计数(幻读)。
- 对比普通
SELECT与SELECT ... FOR UPDATE的差异。
下一篇:锁机制。