You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

JPA的@Transactional注解是否会引发竞态条件?

JPA @Transactional 相关竞态条件问题解答

场景核心行为说明

首先明确:@Transactional是事务声明注解,仅负责管理事务的生命周期,实际的加锁、提交、回滚逻辑完全由底层数据库实现,和JPA上层框架无关。
你描述的场景行为完全取决于数据库的事务隔离级别:

  • 绝大多数生产环境使用的默认隔离级别(MySQL InnoDB为READ_COMMITTED,Oracle为READ_COMMITTED)下,Program A执行UPDATE语句时,数据库会自动对X行加行级写锁,该锁会一直持有到A的事务提交或回滚完成才会释放。此时你描述的步骤2「Program B在A更新完成后修改X行」不可能发生,B的写请求会一直阻塞,直到A的事务结束。

仅当你主动将事务隔离级别设置为生产环境几乎不用的READ_UNCOMMITTED(读未提交)时,才会出现B可以读取A未提交的修改、并且在A回滚前修改X行的情况。


回滚后X行的状态

  • 常规隔离级别(READ_COMMITTED及以上):A回滚后X行会直接恢复到A事务启动前的状态,阻塞中的B的写操作才会开始执行,不会出现数据冲突。
  • READ_UNCOMMITTED隔离级别:会触发脏写问题,A的回滚会直接覆盖B已经提交的修改,导致B的更新丢失,这就是你担心的竞态条件,属于隔离级别配置不当导致的问题,和@Transactional本身无关。

@Transactional的加锁规则

@Transactional本身不会主动加锁,数据库的加锁仅和执行的SQL类型有关:

  • 事务内执行UPDATE/DELETE等写语句时,数据库会自动对涉及的行加行级写锁,持有到事务结束,期间其他事务无法修改对应行。
  • 只读事务默认不会加锁,如果你需要禁止并发写入,需要主动使用悲观锁(如JPA的@Lock(LockModeType.PESSIMISTIC_WRITE))或乐观锁(如@Version版本字段)机制。

自定义更新行数校验的回滚逻辑

你的判断是正确的:

  • JPA默认会将写操作攒到事务提交前批量刷入数据库(flush),但如果你的UPDATE方法加了Spring Data JPA的@Modifying注解、或者主动调用entityManager.flush()、或者关闭了JDBC批量写入配置,UPDATE语句会立即执行,你可以拿到准确的更新行数。
  • 只要你抛出的是RuntimeException类型的异常(@Transactional默认仅对非受检异常触发回滚,受检异常需要主动配置rollbackFor属性),无论UPDATE语句是否已经执行到数据库,事务都会完整回滚,不会残留脏数据。

内容的提问来源于stack exchange,提问作者Tim

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.05 21:27:00