Appearance
隔离级别和幻读问题怎么放到 InnoDB 里看
很多人学事务隔离级别时,会把结论背成一张表:
- RU 会脏读
- RC 会不可重复读
- RR 会幻读
- Serializable 最强
这张表在考试里够用,但在 InnoDB 里远远不够。
因为 MySQL 真正影响你线上现象的,不只是“隔离级别名字”,而是:
- 普通
select还是当前读 - MVCC 还是加锁读
- 索引条件是什么
- 间隙锁和临键锁有没有介入
先说结论
- InnoDB 下理解隔离级别,先区分快照读和当前读
Repeatable Read下普通查询大多靠 MVCC 避免“看到新增行”的问题,但加锁读仍然要靠间隙锁 / 临键锁处理并发插入- 幻读不是一句“RR 就没有”能解释完的,要看你执行的是哪类 SQL
- 线上排查事务问题时,隔离级别、索引命中方式、锁范围必须一起看
隔离级别到底在保护什么
事务隔离级别本质上是在回答一个问题:
同一时间多个事务同时读写时,数据库允许你看到多“新”的数据,又要为此付出多大的并发控制成本。
它保护的是并发读写下的观察一致性,不是业务最终一致性的全部。
先把四种隔离级别放进一个直观框架
Read Uncommitted
几乎不做像样的隔离,可能读到别的事务尚未提交的数据。
实际生产很少作为业务默认选项。
Read Committed
每次读已提交版本。
能避免脏读,但同一事务内两次普通查询可能读到不同结果。
Repeatable Read
InnoDB 默认隔离级别。
普通快照读在一个事务内通常保持一致。
Serializable
最强隔离,但并发代价也最大。
很多场景会退化成更强的锁控制。
脏读、不可重复读、幻读不要死记
脏读
读到了别人还没提交的数据。
不可重复读
同一事务里,两次读取同一行记录,结果变了。
幻读
同一事务里,两次按条件查询,第二次突然多出或少了“满足条件的行集合”。
和不可重复读的区别在于:
- 不可重复读更像“同一行内容变了”
- 幻读更像“结果集成员变了”
但 InnoDB 里一定要加一句: 看你是怎么读的
这是最关键的。
普通 select
通常属于快照读,也叫一致性读。
它走 MVCC 和 Read View。
select for update / update / delete
这类属于当前读。
它关心的是“最新值 + 锁控制”。
所以你不能脱离 SQL 类型直接讨论“有没有幻读”。
为什么很多人会误会 RR 完全没有幻读
因为在 InnoDB 的 RR 下,普通快照读大多确实能避免经典教材里的幻读体验。
例如:
- 事务 T1 普通
select - 事务 T2 插入满足条件的新记录并提交
- T1 再做普通
select
在 RR 下,T1 仍然可能只看到第一次快照时的结果集。
这让人感觉“幻读没了”。
但如果换成当前读场景,例如:
select ... for updateupdate where ...
就要靠间隙锁或临键锁限制并发插入,否则语义就会被破坏。
间隙锁和临键锁为什么要引入
如果只锁住已经存在的记录,对“满足范围条件但尚未存在的新记录”是没约束力的。
例如你执行:
sql
select * from order_tbl
where amount between 100 and 200
for update;如果数据库只锁住现有记录,别的事务仍可能插入一条 amount = 150 的新数据。
这样你的“范围锁定”其实不完整。
所以 InnoDB 会在合适条件下引入:
- 间隙锁: 锁住记录之间的间隙
- 临键锁: 记录锁 + 间隙锁的组合
它们的作用不是“让你更难并发”,而是保证范围条件下的当前读语义成立。
为什么索引会强烈影响锁范围
这是线上排查非常重要的一点。
如果你的条件能命中合适索引,锁范围通常更可控。
如果不能命中索引,InnoDB 可能扩大扫描和加锁范围。
这会直接带来:
- 锁冲突变多
- 死锁概率上升
- 你以为只锁一小段,实际上锁了一大片
所以事务问题不能只看隔离级别,还要看:
- SQL 条件
- 索引设计
- 执行计划
一个业务上很常见的误判
很多人看到“重复提交”或“超卖”问题,就说:
“把隔离级别调高就行。”
这通常不够。
你还得看:
- 是快照读问题,还是当前读问题
- 是否需要显式加锁
- 是否有唯一约束、版本号、幂等键辅助
数据库隔离级别不是万能业务并发开关。
线上怎么判断是隔离级别问题还是锁设计问题
我一般会按这个顺序看:
- 当前事务的隔离级别是什么
- 出问题的 SQL 是普通查询还是加锁读
- 条件是否命中索引
- 锁冲突、死锁和慢事务是否同步出现
- 业务是否把“快照读看到的结果”误当成“当前可更新事实”
很多“隔离级别问题”,最后其实是把快照读结果直接拿来做并发决策了。
工程上更稳的建议
- 默认先理解 InnoDB 的 RR,而不是只背 ANSI 隔离表
- 业务更新关键数据时,明确是否需要当前读
- 把索引设计和事务语义放在一起看
- 长事务越少越好,避免把锁和版本链拖长
总结
在 InnoDB 里理解隔离级别,不能停留在教材里的四个名字。
真正有用的理解路径是:
隔离级别 -> 快照读 / 当前读 -> MVCC / 锁 -> 索引命中方式 -> 最终看到的并发现象
把这条链建立起来之后,幻读、锁冲突和事务异常才会真正变得可解释。