如何处理 丢失更新(不可重复读)

简单来说,就是在一个事务的生命周期内,其读取的数据被另一个并发事务修改并提交,导致数据状态发生变化,而当前事务基于已过时的数据进行了更新。

原因与问题分析

下表清晰地展示了这个问题的核心原因以及可能引发的具体并发问题:

问题点 描述 可能引发的并发问题
默认隔离级别 Spring中@Transactional的默认隔离级别是Isolation.DEFAULT,这通常意味着使用数据库的默认级别(MySQL为可重复读(REPEATABLE_READ) ,Oracle为读已提交(READ_COMMITTED))。 读已提交 级别下,会发生不可重复读 ;在可重复读 级别下,虽然能避免不可重复读,但仍可能发生幻读,并且如果更新逻辑设计不当,依然可能导致数据覆盖。
核心问题 您描述的场景关键在于:事务A读取数据后,事务B修改并提交了该数据。此时,事务A持有的数据副本已是过时版本。当事务A基于此旧数据计算并进行更新时,就会覆盖事务B已提交的更改,造成数据丢失。 丢失更新​ 。

解决方案

解决这个问题,主要有以下几种策略,您可以根据业务场景的并发冲突概率和性能要求进行选择。

解决方案 实现方式 适用场景
1. 提高事务隔离级别 将方法的事务隔离级别设置为Isolation.SERIALIZABLE(最高级别)。这会强制事务串行执行,彻底解决并发问题,但性能开销最大,一般不推荐。 对数据一致性要求极高,且并发量不大的场景。
2. 使用悲观锁 在查询数据的SQL语句后添加FOR UPDATE(如:SELECT * FROM table WHERE ... FOR UPDATE)。这会直接锁定目标行,阻止其他事务修改,直到当前事务结束。 并发冲突频繁,且事务执行时间较短的场景。这是一种"先锁后改"的策略。
**3. 使用乐观锁(推荐)**​ 在数据库表中增加一个version(版本号)字段。更新时,在SQL语句中同时检查版本号是否未变,并将版本号+1。例如:UPDATE table SET ..., version = version + 1 WHERE id = ? AND version = ?。如果版本号不匹配,则更新失败。 并发冲突不频繁的大多数场景。性能最好,是一种"先改再看"的策略。
4. 使用分布式锁 对于分布式系统,可以使用如Redisson等工具,在业务层对关键资源(如订单ID)加锁,确保同一时间只有一个线程能执行"查询-计算-更新"的完整流程。 分布式应用环境,需要跨JVM保证资源独占。

如何选择?

  • 如果您的应用并发冲突概率不高乐观锁 通常是最佳选择,因为它性能最好,实现也相对简单。

  • 如果冲突非常频繁,或者事务本身很短 :可以考虑使用悲观锁FOR UPDATE)。

  • 一般情况下,不建议使用SERIALIZABLE隔离级别,因为对数据库性能影响太大。

希望这些解释和方案能帮助您更好地处理Spring事务中的并发问题。如果您想了解某个方案的具体代码实现细节,我可以为您提供更详细的说明。

相关推荐
青衫码上行1 分钟前
maven依赖管理和生命周期
java·学习·maven
散峰而望4 分钟前
OJ 题目的做题模式和相关报错情况
java·c语言·数据结构·c++·vscode·算法·visual studio code
长安城没有风5 分钟前
Java 高并发核心编程 ----- 初识多线程(上)
java·juc
董世昌417 分钟前
HTTP协议中,GET和POST有什么区别?分别适用什么场景?
java·开发语言·前端
独自破碎E7 分钟前
Java中HashMap的默认负载因子为什么设置为0.75?
java·开发语言·网络
疋瓞8 分钟前
C/C++查缺补漏《5》_智能指针、C和C++中的数组、指针、函数对比、C和C++中内存分配概览
java·c语言·c++
幽络源小助理10 分钟前
SpringBoot+Vue大学城水电管理系统源码 | 后勤设备管理 | 幽络源
java·开发语言
黎雁·泠崖20 分钟前
Java数组进阶:内存图解+二维数组全解析(底层原理+Java&C差异对比)
java·c语言·开发语言
Remember_99321 分钟前
【JavaSE】一站式掌握Java面向对象编程:从类与对象到继承、多态、抽象与接口
java·开发语言·数据结构·ide·git·leetcode·eclipse
小园子的小菜24 分钟前
Spring事务失效9大场景(Java面试高频)
java·spring·面试