事务隔离级别与幻读
并发事务会出现三类经典问题:脏读(读到未提交数据)、不可重复读(两次读到的值不同)、幻读(两次读到的行数不同)。四个隔离级别就是逐步收紧这三类问题的方案。
四个级别
| 级别 | 脏读 | 不可重复读 | 幻读 |
|---|---|---|---|
| READ UNCOMMITTED | 可能 | 可能 | 可能 |
| READ COMMITTED | 不会 | 可能 | 可能 |
| REPEATABLE READ(InnoDB 默认) | 不会 | 不会 | 基本不会 |
| SERIALIZABLE | 不会 | 不会 | 不会 |
InnoDB 如何做到「基本不会」
InnoDB 在 REPEATABLE READ 下靠两套机制:
MVCC(快照读):普通 SELECT 读的是事务开始时的快照。其他事务新插入的行,因创建版本号更新,当前事务看不到——幻读在快照读里被消除了。
间隙锁(当前读):SELECT ... FOR UPDATE 这类锁定读会锁住已有记录之间的间隙,阻止其他事务在范围内插入。
但两者边界容易被忽略:
-- 事务 A(RR 级别)
SELECT * FROM t WHERE id > 10; -- 快照读,看到 N 行
SELECT * FROM t WHERE id > 10 FOR UPDATE; -- 当前读,能看到新提交的行
第一次是快照,第二次是当前读,两者结果可以不一致。同一个事务里混用两种读,就绕过了 MVCC 的保护。
实践建议
- 需要严格一致的「读后写」场景,统一用当前读(
FOR UPDATE),不要先普通 SELECT 再写; UPDATE/DELETE本身是当前读,条件里的判断要与快照读分开对待;- 隔离级别保持默认 RR 即可,RC 在云数据库场景有它的优势,但需要清楚放弃了什么。
一个常被问到的问题
RR 下死锁变多怎么办?多数死锁源于「不同顺序加锁」。约定统一按主键顺序更新,并把事务控制在最小范围,能解决大部分死锁。SHOW ENGINE INNODB STATUS 的 LATEST DETECTED DEADLOCK 段是排查起点。