MySQL事务隔离级别详解
隔离级别是数据库在并发正确性与吞吐量之间的调节旋钮:级别越高,数据越”干净”,并发越受限。InnoDB 默认的 REPEATABLE READ 借助 MVCC 和 Next-Key Lock,在多数场景下既保住了正确性又没牺牲太多性能——但”默认够用”的前提是你要懂它防住了什么、没防住什么。
它解决什么问题
银行转账是教科书式的例子:从 A 扣 100、给 B 加 100,两步必须同生共死,中间状态绝不能让第三个事务看见。只要多个事务并发读写同一份数据,就可能出现三类读异常——脏读、不可重复读、幻读。隔离级别就是 SQL 标准给出的四档解决方案,回答的问题是:一个事务究竟能在多大程度上”看见”别人未提交或刚提交的修改。
核心原理:ACID 与一致性的边界
谈隔离级别前必须先把 ACID 讲透,尤其是一致性(Consistency)这个最常被误读的概念:
- 原子性(Atomicity):事务内的操作要么全部生效,要么全部回滚,不存在”执行了一半”的落库状态。InnoDB 靠 undo log 实现——回滚时按日志把数据改回去。
- 一致性(Consistency):事务把数据库从一个满足所有完整性约束的状态,带到另一个同样合法的状态。注意它的边界:一致性是目标,A、I、D 是手段。数据库能保证主键不重复、外键有依托、级联规则被遵守,但”转账前后两人余额之和不变”这类业务语义,数据库本身无从知晓,需要应用层把扣款和加款包进同一个事务里,一致性才成立。换句话说,C 是 AID 叠加应用正确性的联合产物,单靠引擎给不了。
- 隔离性(Isolation):并发事务彼此不串味,由锁和 MVCC 共同实现,四个隔离级别就是对它的四档量化。
- 持久性(Durability):提交成功的修改不丢,靠 redo log 的 WAL 机制保证——先写日志、后刷数据页,宕机后按日志重放。
深入机制:三种读异常与四档隔离
脏读、不可重复读、幻读
用一张 account(id, balance) 表演示,两个会话并行操作:
- 脏读:事务 A 改了余额还没提交,事务 B 就读到了这个中间值;A 随后回滚,B 手里是一个从未真实存在过的数据。读到了”别人的草稿”,是最危险的一类。
- 不可重复读:事务 A 在同一事务里两次读同一行,中间事务 B 修改并提交了这行,A 两次读到的值不一样。问题出在 B 对已存在行的 UPDATE/DELETE。
- 幻读:事务 A 按同一范围条件两次查询,中间事务 B 插入(或删除)了满足条件的行并提交,A 第二次查多出了”幻影行”。问题出在 B 的 INSERT 落在 A 的查询范围内。
一句话区分:不可重复读针对”同一行变了”,幻读针对”同一范围里行的集合变了”。
四个隔离级别
| 隔离级别 | 脏读 | 不可重复读 | 幻读 |
|---|---|---|---|
| READ UNCOMMITTED | 可能 | 可能 | 可能 |
| READ COMMITTED | 杜绝 | 可能 | 可能 |
| REPEATABLE READ | 杜绝 | 杜绝 | 标准定义下可能,InnoDB 基本防住 |
| SERIALIZABLE | 杜绝 | 杜绝 | 杜绝 |
- READ UNCOMMITTED:读不加锁也不看版本,几乎无实用价值。
- READ COMMITTED:每次一致性读都生成新快照,只能看到读之前已提交的数据,Oracle、PostgreSQL、SQL Server 的默认级别。
- REPEATABLE READ:InnoDB 默认级别,事务内第一次一致性读建立快照并沿用到提交,同一份数据反复读结果恒定。
- SERIALIZABLE:所有读都隐式加共享锁,事务退化为排队执行,正确性满分、并发性垫底,一般只在 XA 分布式事务等严苛场景使用。
查看当前级别:MySQL 8.0 起用 SELECT @@transaction_isolation;,更早版本是 @@tx_isolation。
InnoDB 在 RR 下如何防幻读
标准 SQL 的 RR 并不要求防幻读,InnoDB 靠两套机制把它”超额完成”了:
- 快照读走 MVCC:普通 SELECT 不碰锁,读的是 ReadView 圈定的历史版本。RR 下 ReadView 在事务首次一致性读时生成且不再更换,别的事务中途插入的新行版本号晚于快照,天然不可见——快照读层面没有幻读。
- 当前读走 Next-Key Lock:
SELECT ... FOR UPDATE、LOCK IN SHARE MODE、INSERT/UPDATE/DELETE 读的是最新版本,必须防别人往范围里插。Next-Key Lock 是记录锁加间隙锁的组合,锁定左开右闭区间(前一条记录, 当前记录],把可能被插入的空隙一并锁死,其他事务的插入只能排队等待——间隙不存在,幻影行无从落地。
-- 会话 A:范围当前读会锁住 id 在 (10, 30] 的记录及间隙 |
要注意的边界:MVCC 管快照读、锁管当前读,两条防线各自独立。如果同一事务里先快照读、再对同一范围当前读,两次结果仍可能不同——这不叫 RR 失效,而是两种读语义本就不同。
工程实践
- 默认 RR 对绝大多数 OLTP 够用,不要为了”听说 RC 快”全局切换;
- RC 的真实优势在于没有间隙锁(唯一性冲突检查等少数场景除外),锁竞争和死锁概率更低,配合 ROW 格式 binlog 数据一致性也有保障,因此不少高并发互联网系统刻意选 RC;
- 长事务是隔离性的大敌:快照钉住旧版本,undo 无法 purge,还会导致锁持有时间过长;
- RR 下间隙锁扩大加锁范围,死锁概率上升,应用要捕获错误码 1213 做有限次重试,
innodb_lock_wait_timeout按业务容忍度调。
对比与权衡
| 维度 | RC | RR |
|---|---|---|
| 快照生成 | 每条一致性读重建 | 事务内首次一致性读建立后沿用 |
| 间隙锁 | 基本不加 | 范围当前读加 Next-Key Lock |
| 幻读(快照读) | 存在 | 消除 |
| 死锁概率 | 较低 | 较高 |
| 典型用户 | 多数非 MySQL 系数据库默认 | InnoDB 默认 |
面试视角
Q1:ACID 里一致性和其他三个是什么关系? 一致性是目标,原子性、隔离性、持久性是数据库提供的手段;但数据库只能兜底约束级的一致性,”转账总额不变”这类业务一致性必须由应用把相关操作圈进同一事务才能成立。
Q2:不可重复读和幻读的区别? 前者是同一行被并发事务 UPDATE/DELETE 后两次读值不同,后者是同一范围条件两次查出行数不同,根源是并发事务的 INSERT。防御手段也不同:前者锁住行即可,后者必须锁间隙。
Q3:InnoDB 的 RR 怎么防幻读? 分两条线:快照读靠 MVCC 的固定 ReadView,晚于快照的版本不可见;当前读靠 Next-Key Lock 锁住记录和间隙,让插入无处发生。
Q4:RR 下还会出现”幻读”现象吗? 会,但场景很窄:同一事务先快照读再对同范围做当前读,或者自身做了 UPDATE 后看到了新插入的行。这是读语义切换导致的可见性跳变,不是机制漏洞。
Q5:为什么很多团队线上用 RC? RC 基本不用间隙锁,锁冲突少、死锁少、并发更高;在 ROW 格式 binlog 下主从一致性有保障。代价是同一事务内两次读可能看到不同结果,业务需能接受。
小结
ACID 里一致性是目的、AID 是手段;脏读、不可重复读、幻读分别对应”读到草稿、同一行变值、同一范围多行”三类并发异常。InnoDB 默认 RR 用 MVCC 的快照和 Next-Key Lock 的区间封锁把三类问题基本都摁住了。选型时记住:RR 求稳、RC 求并发,真正要警惕的不是级别本身,而是长事务和混用快照读与当前读。

