Skip to content
MySQL 存储引擎与执行细节 · 第 2 篇 / 共 4 篇
领域数据与中间件
专题MySQL 专题
当前序列MySQL 存储引擎与执行细节
阅读位置第 2 篇 / 共 4 篇当前专题第 3 个序列 / 共 7 个序列

隔离级别和幻读问题怎么放到 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 下,普通快照读大多确实能避免经典教材里的幻读体验。

例如:

  1. 事务 T1 普通 select
  2. 事务 T2 插入满足条件的新记录并提交
  3. T1 再做普通 select

在 RR 下,T1 仍然可能只看到第一次快照时的结果集。
这让人感觉“幻读没了”。

但如果换成当前读场景,例如:

  • select ... for update
  • update where ...

就要靠间隙锁或临键锁限制并发插入,否则语义就会被破坏。

间隙锁和临键锁为什么要引入

如果只锁住已经存在的记录,对“满足范围条件但尚未存在的新记录”是没约束力的。

例如你执行:

sql
select * from order_tbl
where amount between 100 and 200
for update;

如果数据库只锁住现有记录,别的事务仍可能插入一条 amount = 150 的新数据。
这样你的“范围锁定”其实不完整。

所以 InnoDB 会在合适条件下引入:

  • 间隙锁: 锁住记录之间的间隙
  • 临键锁: 记录锁 + 间隙锁的组合

它们的作用不是“让你更难并发”,而是保证范围条件下的当前读语义成立。

为什么索引会强烈影响锁范围

这是线上排查非常重要的一点。

如果你的条件能命中合适索引,锁范围通常更可控。
如果不能命中索引,InnoDB 可能扩大扫描和加锁范围。

这会直接带来:

  • 锁冲突变多
  • 死锁概率上升
  • 你以为只锁一小段,实际上锁了一大片

所以事务问题不能只看隔离级别,还要看:

  • SQL 条件
  • 索引设计
  • 执行计划

一个业务上很常见的误判

很多人看到“重复提交”或“超卖”问题,就说:

“把隔离级别调高就行。”

这通常不够。
你还得看:

  • 是快照读问题,还是当前读问题
  • 是否需要显式加锁
  • 是否有唯一约束、版本号、幂等键辅助

数据库隔离级别不是万能业务并发开关。

线上怎么判断是隔离级别问题还是锁设计问题

我一般会按这个顺序看:

  1. 当前事务的隔离级别是什么
  2. 出问题的 SQL 是普通查询还是加锁读
  3. 条件是否命中索引
  4. 锁冲突、死锁和慢事务是否同步出现
  5. 业务是否把“快照读看到的结果”误当成“当前可更新事实”

很多“隔离级别问题”,最后其实是把快照读结果直接拿来做并发决策了。

工程上更稳的建议

  • 默认先理解 InnoDB 的 RR,而不是只背 ANSI 隔离表
  • 业务更新关键数据时,明确是否需要当前读
  • 把索引设计和事务语义放在一起看
  • 长事务越少越好,避免把锁和版本链拖长

总结

在 InnoDB 里理解隔离级别,不能停留在教材里的四个名字。
真正有用的理解路径是:

隔离级别 -> 快照读 / 当前读 -> MVCC / 锁 -> 索引命中方式 -> 最终看到的并发现象

把这条链建立起来之后,幻读、锁冲突和事务异常才会真正变得可解释。

延伸阅读相关文章优先当前专题,再补跨专题关联。
同一序列 · 顺着当前主线继续读覆盖索引和最左前缀原则怎么真正用起来适合把 MVCC、索引、排序聚合和日志系统放在一起深入看。MySQL 专题 · MySQL 存储引擎与执行细节同一序列 · 顺着当前主线继续读ORDER BY、GROUP BY 为什么容易慢适合把 MVCC、索引、排序聚合和日志系统放在一起深入看。MySQL 专题 · MySQL 存储引擎与执行细节同专题其他序列 · MySQL 变更与运维治理大表在线 DDL 怎么做更稳适合把计数、日志、在线变更和归档清理放在一起看。MySQL 专题 · MySQL 变更与运维治理同专题其他序列 · MySQL 变更治理与数据生命周期大事务为什么会拖垮数据库适合把大事务、DDL、回填、慢 SQL 治理和归档分层放进一条持续治理主线上看。MySQL 专题 · MySQL 变更治理与数据生命周期跨专题关联 · 同场景:基础学习@Conditional 系列注解怎么配合使用适合把自动装配、条件装配、Profile 和循环依赖放回 Spring Boot 启动过程里理解。Spring 专题 · Spring Boot 启动、装配与配置跨专题关联 · 同场景:基础学习保留策略和日志压缩适合哪些场景适合把副本机制、acks、批量压缩和日志组织放在一条 Kafka 投递主线上理解。Kafka 专题 · Kafka 投递链路与日志存储机制
继续阅读MySQL 存储引擎与执行细节当前序列第 2 篇 / 共 4 篇当前专题第 3 个序列 / 共 7 个序列
往前看
上一篇MVCC 和 Read View 怎么理解回到当前序列上一章上一序列MySQL 事务、锁与高可用从第 1 篇开始:MySQL 事务与锁
往后看
下一篇覆盖索引和最左前缀原则怎么真正用起来继续当前序列下一章下一序列MySQL 变更与运维治理从第 1 篇开始:COUNT(*) 和 COUNT(列) 到底差在哪

把零散经验整理成可查、可复用、可持续更新的企业级知识门户