从 READ_UNCOMMITTED 到 SERIALIZABLE:Spring 事务隔离级别全解析

原文来自于:zha-ge.cn/java/119

从 READ_UNCOMMITTED 到 SERIALIZABLE:Spring 事务隔离级别全解析

有一次线上出了个"灵异事件":订单明明查出来是 0 元,用户却支付成功了。运维一脸懵逼:"数据库你是不是给玩坏了?"

我当时心里咯噔一下:这八成是事务隔离级别的问题。

别笑,这种 Bug 在刚入行的时候我也见怪不怪------很多人对事务隔离的理解,基本停留在"读未提交""读已提交"这四个词上。可在真实项目里,隔离级别决定的是系统能不能稳、数据会不会脏、Bug 会不会连锁反应。


那些年我没搞懂的"事务隔离"

还记得第一次用 Spring 事务的时候,我的注解是这样写的:

java 复制代码
@Transactional
public void createOrder() {
    // ...
}

就完事了。

我从没管过"隔离级别"是什么鬼,甚至都不知道 @Transactional 默认的隔离级别是啥。直到那次"0 元订单"事件,我才开始追根溯源,搞懂了数据库的"隔离"到底隔了个啥。


一切的起点:并发带来的三大怪象

多事务并发执行,就可能出现三大"经典怪象":

  • 脏读(Dirty Read):读到了别的事务还没提交的数据。
  • 不可重复读(Non-repeatable Read):同一事务里两次读取同一行,值却变了。
  • 幻读(Phantom Read):同一事务两次查询同条件数据,返回的行数不一样(新增或删除了行)。

事务隔离的本质,就是通过"牺牲一点并发性能"来避免这些怪象。


四大隔离级别全解析

Spring 的事务隔离级别其实就是数据库隔离级别的映射,你可以通过:

java 复制代码
@Transactional(isolation = Isolation.READ_COMMITTED)

来指定,常用的四种如下:


1. READ_UNCOMMITTED(读未提交)------"能看别人草稿"

  • 脏读 ✅ 可能发生
  • 不可重复读 ✅ 可能发生
  • 幻读 ✅ 可能发生

这个级别下,事务可以读到别人还没提交的数据。比如事务 A 修改了余额,但没提交,事务 B 就能"提前看到"这个新值。 一旦 A 回滚,B 的数据就成了"脏数据"。

👉 优点:性能最好 👉 缺点:数据最不可靠(几乎没人敢在生产用)


2. READ_COMMITTED(读已提交)------"只看成品,不看草稿"

  • 脏读 ❌ 不会发生
  • 不可重复读 ✅ 可能发生
  • 幻读 ✅ 可能发生

大多数主流数据库(如 Oracle、PostgreSQL)默认都是这个级别。 事务只能读到已经提交的数据,脏读没了,但同一个事务里两次读同一行数据可能不同,因为别的事务可能在中途修改了它。

👉 优点:脏读没了,性能也还不错 👉 缺点:数据一致性不够强


3. REPEATABLE_READ(可重复读)------"你看到的永远一样"

  • 脏读 ❌ 不会发生
  • 不可重复读 ❌ 不会发生
  • 幻读 ✅ 可能发生

这个级别保证了:一个事务期间内,读到的数据永远不会变。 MySQL(InnoDB)默认就是这个级别,它通过"MVCC(多版本并发控制)"来实现,保证事务里的多次读取结果一致。

但问题是,行数可能变多,比如:

  • 第一次查余额表有 10 行
  • 事务还没结束时,别的事务插入了一行
  • 第二次查出来 11 行 ------ 这就是"幻读"

👉 优点:读一致性更强 👉 缺点:有可能出现幻读


4. SERIALIZABLE(可串行化)------"你一个我一个,别挤"

  • 脏读 ❌ 不会发生
  • 不可重复读 ❌ 不会发生
  • 幻读 ❌ 不会发生

这是隔离级别的"终极防护":所有事务顺序执行,就像排队买奶茶一样,一个一个来。 问题当然也显而易见------性能可能惨不忍睹,吞吐量骤降。

👉 优点:数据一致性最强 👉 缺点:性能最差(几乎不用在高并发业务里)


踩坑瞬间:隔离级别选错,后果很严重

那次"0 元订单"事件的真相是这样的:

  • 事务 A:扣库存
  • 事务 B:计算价格

A 还没提交,B 就读了库存为 0 的"草稿"数据,直接计算出 0 元订单。 事务回滚之后,库存还在,但价格早已算错。

解决方案? 把隔离级别从 READ_UNCOMMITTED 提升到 READ_COMMITTED,问题立刻消失。

这让我意识到,事务隔离级别不是"数据库的内部细节",而是业务正确性的重要保证。


面试官杀手锏回答

问:四个事务隔离级别分别能防止什么问题?

答题模板(建议直接背下来):

  • READ_UNCOMMITTED:啥都不防,脏读、不可重复读、幻读全有
  • READ_COMMITTED:防脏读,不可重复读和幻读可能有
  • REPEATABLE_READ:防脏读、防不可重复读,幻读可能有
  • SERIALIZABLE:全防,但性能最差

再补一句:"Spring 的隔离级别本质上是映射到底层数据库隔离级别的,具体行为依赖数据库实现,比如 MySQL 的 REPEATABLE_READ 已经用 MVCC 部分防住了幻读。"


写在最后:性能与一致性,永远是拉扯的两端

事务隔离级别就像一条滑竿:

  • 一头是数据一致性
  • 一头是系统性能

你越往一致性那头靠,性能就越低;你越追求性能,风险就越高。

所以,选什么隔离级别从来不是背诵题,而是业务决策。 支付系统、金融结算这类强一致性场景,可能会用到 REPEATABLE_READ 甚至 SERIALIZABLE;而高吞吐的日志分析、推荐系统,READ_COMMITTED 也完全够用。

记住一句话:事务隔离没有"最好",只有"最合适"。

相关推荐
云霄IT4 小时前
绕过Frida检测反调试的一些办法
android·javascript
摸着石头过河的石头4 小时前
JavaScript 垃圾收集:内存管理的艺术
前端·javascript
Codelinghu4 小时前
【bug】大模型微调bug:OSError: Failed to load tokenizer.| Lora
后端
Frank_zhou4 小时前
虚拟线程池
后端
aiopencode4 小时前
iOS混淆与IPA加固实战手记,如何构建苹果应用防反编译体系
后端
用户40511197831834 小时前
JSAR 粒子系统实战:打造炫酷 3D 烟花秀
javascript
一树山茶4 小时前
uniapp云函数使用——内容审核
前端·javascript
cxyxiaokui0014 小时前
JDK 动态代理 vs CGLIB:原理、区别与 Spring AOP 底层揭秘
java·后端·spring
西西学代码4 小时前
Flutter---坐标网格图标
前端·javascript·flutter