Skip to content
Spring 事务与治理实践 · 第 1 篇 / 共 2 篇
领域编程与框架
专题Spring 专题
当前序列Spring 事务与治理实践
阅读位置第 1 篇 / 共 2 篇当前专题第 4 个序列 / 共 6 个序列

Spring 事务隔离级别和丢失更新问题

先说结论

  • Spring 事务隔离级别本质上是在使用数据库隔离能力,真正生效的核心仍然是底层数据库
  • 丢失更新不是只靠把隔离级别调高就一定能解决,很多时候还要配合乐观锁、悲观锁或条件更新
  • 排查这类问题时,先分清是“并发读写冲突”还是“业务流程覆盖写”

一、先搞清什么是丢失更新

最常见的场景是:

  1. 事务 A 读出一条记录,值是 10
  2. 事务 B 也读出同一条记录,值也是 10
  3. A 更新成 11
  4. B 也更新成 11

最后结果看起来像只执行了一次更新,这就是典型的丢失更新。

它和脏读、不可重复读、幻读不完全是一回事,重点是:

  • 两次更新互相覆盖了
  • 业务以为自己改成功了
  • 实际有一次更新被“吃掉了”

二、Spring 事务隔离级别到底管到哪

Spring 里的隔离级别配置,本质上只是把要求传给数据库。

也就是说:

  • READ_COMMITTED
  • REPEATABLE_READ
  • SERIALIZABLE

这些真正的行为差异,最终还是由 MySQL、PostgreSQL 这类数据库去决定。

所以要特别注意两件事:

  1. 不同数据库默认隔离级别不一样。
  2. Spring 配了隔离级别,不代表你的业务写法就天然不会丢失更新。

三、为什么很多系统还是会遇到丢失更新

因为实际业务里,更常见的是下面这几种写法:

1. 先查再改

先查库存、余额、状态,再把计算后的结果回写。
这类写法在并发下最容易互相覆盖。

2. 跨层逻辑过长

查出来的数据经过很多业务判断、远程调用、参数拼装后才回写,窗口越长,冲突概率越高。

3. 没有版本号或条件约束

更新语句只是:

sql
update account set balance = ? where id = ?

这种写法本身不具备并发保护能力。

四、解决思路一般分三类

1. 乐观锁

最常见的做法是加 version 字段:

sql
update account
set balance = ?, version = version + 1
where id = ? and version = ?

这样更新失败时,应用就知道这次写入已经过期了。

2. 悲观锁

例如 select ... for update
适合强一致、冲突概率高、临界资源很明确的场景。

但代价是:

  • 等锁时间更长
  • 吞吐下降
  • 死锁风险上升

3. 原子条件更新

有些库存、计数类场景,其实不用先查再改,可以直接写成:

sql
update stock
set count = count - 1
where id = ? and count > 0

这种方式往往比在 Java 里先读后算更稳。

五、最容易踩的坑

1. 以为 @Transactional 就自动防并发覆盖

事务能保证一段逻辑在一个事务里提交或回滚,但不等于自动解决并发写冲突。

2. 一上来就调成串行化

SERIALIZABLE 能更强,但对吞吐和锁竞争影响很大,不适合当默认止血手段。

3. 只盯数据库,不看业务更新方式

很多丢失更新问题,根因其实是业务先查再改的写法,而不是数据库隔离级别太低。

总结

Spring 事务隔离级别解决的是事务读写可见性问题,丢失更新更多是并发写控制问题。真正稳的做法通常不是单纯调高隔离级别,而是结合乐观锁、悲观锁或原子条件更新,把“并发覆盖写”这件事直接收住。

延伸阅读相关文章优先当前专题,再补跨专题关联。
同一序列 · 顺着当前主线继续读Spring Cache 注解怎么用更稳适合把事务隔离、缓存和异步边界放在一起看。Spring 专题 · Spring 事务与治理实践同专题其他序列 · 共享标签:MySQLSpring 事务传播行为怎么理解适合把 Spring Boot 开发习惯和事务边界问题放在一起看。Spring 专题 · Spring Boot 与事务实践同专题其他序列 · Spring Boot 启动、装配与配置@Conditional 系列注解怎么配合使用适合把自动装配、条件装配、Profile 和循环依赖放回 Spring Boot 启动过程里理解。Spring 专题 · Spring Boot 启动、装配与配置同专题其他序列 · Spring Web 链路与治理能力@Transactional 和 @Async 一起用为什么容易踩坑适合把分发链路、鉴权机制、异步事务和接口治理放在同一条 Web 主线上整理。Spring 专题 · Spring Web 链路与治理能力跨专题关联 · 同场景:基础学习聚合根和事务边界怎么划分适合把 DDD、Saga、TCC 和 BFF、网关边界放在一起看。架构与设计专题 · 分布式系统取舍与边界跨专题关联 · 同场景:基础学习冷热分层和索引生命周期怎么设计适合把 refresh、merge、冷热分层和批量写入放到一起看。Elasticsearch 专题 · Elasticsearch 写入与分层架构
继续阅读Spring 事务与治理实践当前序列第 1 篇 / 共 2 篇当前专题第 4 个序列 / 共 6 个序列
往前看
上一序列Spring 容器核心机制从第 1 篇开始:BeanFactory 和 ApplicationContext 到底是什么关系
往后看
下一篇Spring Cache 注解怎么用更稳继续当前序列下一章下一序列Spring Boot 启动、装配与配置从第 1 篇开始:Spring Boot 自动装配到底是怎么生效的

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