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
相关产品推荐
相关产品推荐

