大家好,我是十三!欢迎来到十三Tech。

前几年接手一个从 Oracle 迁移到 MySQL 的项目——开发以为 MySQL 的 RR(Repeatable Read)跟 Oracle 的 Serializable 等价,结果在压测时遇到"幻读"现象(同一事务里两次范围查询结果集不同),花了三天才搞明白根因。后来认真读了 SQL 标准和 InnoDB 的实现差别,才知道——MySQL 的 RR 不是 SQL 标准的 RR,它额外用间隙锁防了幻读

这件事让我意识到,很多人讲隔离级别只讲"四个级别名字"——太浅。隔离级别真正要理解的是它防什么、不防什么、各自代价是什么。这一篇就聊聊 SQL 标准定义的三个异常、四个级别,以及 InnoDB 实现跟标准的差别。

四个隔离级别与三种异常

一、SQL 标准 — 三种异常 + 四个级别

SQL 标准定义了三种"并发事务下可能出现的异常",按"严重程度"递减:

脏读(Dirty Read):事务 A 读到了事务 B 未提交的修改——B 后来回滚,A 读到的就是"从未存在过"的数据。生产中几乎不会遇到,因为最低级别(读未提交)都没人用。

不可重复读(Non-Repeatable Read):事务 A 两次读同一行,结果不同——因为中间事务 B 修改并提交了。注意是"同一行结果变化"。

幻读(Phantom Read):事务 A 两次执行同一范围查询,结果集行数不同——因为中间事务 B 插入或删除了符合条件的行。注意是"结果集行数变化",不是单行变化。

SQL 标准据此定义四个隔离级别:

级别 脏读 不可重复读 幻读
Read Uncommitted(读未提交) 可能 可能 可能
Read Committed(读已提交) 防止 可能 可能
Repeatable Read(可重复读) 防止 防止 可能
Serializable(串行化) 防止 防止 防止

这个表是 SQL 标准——很多教科书到这里就结束了。但InnoDB 的实现并不严格遵循这个表——这就是下一篇要讲的"坑"。

SQL 标准与 InnoDB 实现的差别

二、InnoDB 实现 — RR 实际上比标准强

InnoDB 对 SQL 标准做了"超额实现"——主要在 RR 级别上。

Read Uncommitted(RU):基本跟标准一致——允许脏读。生产没人用

Read Committed(RC):跟标准一致——防止脏读,允许不可重复读和幻读。Oracle 默认就是这个级别。

Repeatable Read(RR)——InnoDB 在这里超额:标准说 RR 仍可能幻读,但 InnoDB 的 RR 用间隙锁防止幻读。这就是为什么开头那个 Oracle → MySQL 项目踩坑——开发以为 RR 会有幻读,实际没有。

Serializable:InnoDB 实现成"所有普通 SELECT 隐式转成 SELECT ... LOCK IN SHARE MODE"——所有读都加共享锁,串行执行。生产几乎不用——并发性太差。

-- 看当前隔离级别
SELECT @@transaction_isolation;  -- 默认 RR
SHOW VARIABLES LIKE 'transaction_isolation';

InnoDB 不严格按标准实现的原因——SQL 标准是 1992 年的,当时的数据库还在用表锁实现并发;InnoDB 用 MVCC + 行锁 + 间隙锁,能用更低代价达到更严格的隔离。理解这一点很重要——InnoDB 的 RR 已经接近 Serializable 的效果,但性能好得多。这就是为什么 MySQL 默认 RR 而不是 RC——RR 的"防幻读"对业务更安全。

RR 不是完美——RR 下仍然有"写偏序"(Write Skew)等更微妙的异常。这些异常在 SQL 标准里没定义,但实际业务会遇到——A08 讲过几个例子。

同一事务在不同隔离级别下的表现

三、怎么选 — 默认 RR 还是改 RC

讲完标准与实现的差别,再说生产中怎么选——这是工程师真正要做的决定。

MySQL 默认 RR 的理由:从库复制基于 binlog,早期 STATEMENT 格式在 RC 下会导致主从数据不一致(依赖事务顺序)。8.0 默认 ROW 格式后,RC 的复制问题消失了,但默认值还是 RR——历史包袱。

为什么很多互联网公司改 RC

  1. 间隙锁代价:RR 下所有范围查询都加间隙锁,并发性降低。RC 下没有间隙锁,并发更好
  2. 死锁少:RR 的间隙锁是死锁高发区(A12 详细讲)。RC 几乎没有间隙锁死锁
  3. STATEMENT binlog 已不推荐:8.0 默认 ROW,RC 的复制问题不再存在
  4. 半同步复制 + ROW 已经解决了主从一致性问题

改 RC 的代价

  1. 不可重复读:同一事务里两次读结果可能不同——业务要能接受
  2. 没有间隙锁保护:范围查询下的"插入"会"幻读"——业务要能接受
  3. 从库延迟可能更敏感:RC 下半同步复制的语义略有变化

判断口诀

  • 业务能接受"事务内不可重复读" + 追求高并发 → RC
  • 业务需要"事务内严格一致" + 不追求极致并发 → RR
  • 金融 / 强一致性场景 → RR + 显式 SELECT FOR UPDATE 兜底
  • 永远不要用 Serializable 和 RU

怎么选 RC vs RR

回头看那个 Oracle → MySQL 项目——开发的误区是把"MySQL 的 RR"等同于"SQL 标准的 RR",没意识到 InnoDB 已经用间隙锁防了幻读。理解了 SQL 标准和 InnoDB 实现的差别后,那个压测里的"幻读现象"根因才浮现:那是 RC 下才会出现的现象,但他们的隔离级别是 RR——所以根因不是隔离级别,是某条 SELECT 用了 FOR UPDATE(当前读),当前读每次都看最新——才会出现"两次结果不同"。

隔离级别真正教会我的,不是"四个级别名词",而是所有"为什么我的查询结果不一样"的根因都在这里——理解标准与实现的差别,能解释 90% 的并发异常现象。

下一篇讲锁——它是隔离级别背后的真正执行机制。


关于十三Tech 资深服务端研发,AI 实践者,专注分享真实可落地的技术经验。 相信 AI 是程序员的最佳搭档。

联系方式:569893882@qq.com GitHub:@TriTechAI