Spring Boot两事务更新同一行时事务挂起问题排查
问题:同一行数据跨独立事务更新时出现挂起的原因分析
我尝试通过编程方式管理两个独立事务来更新同一行数据,测试代码如下:
@Sql("classpath:sql/balance/account_with_balance_EE.sql") @Test void changeNoLock() { long balanceId = 10000L; DefaultTransactionDefinition firstTransactionDefinition = new DefaultTransactionDefinition(); DefaultTransactionDefinition secondTransactionDefinition = new DefaultTransactionDefinition(); firstTransactionDefinition.setName("firstTransaction"); secondTransactionDefinition.setName("secondTransaction"); firstTransactionDefinition.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW); secondTransactionDefinition.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW); TransactionStatus firstTransactionStatus = txManager.getTransaction(firstTransactionDefinition); balanceService.change(balanceId, BigDecimal.ONE); TransactionStatus secondTransactionStatus = txManager.getTransaction(secondTransactionDefinition); balanceService.change(balanceId, BigDecimal.ONE); txManager.commit(firstTransactionStatus); txManager.commit(secondTransactionStatus); BalanceDto balanceDto = balanceService.find(balanceId); assertEquals(new BigDecimal("2.00"), balanceDto.getBalanceAmount()); }
balanceService#change方法使用默认的事务传播行为和隔离级别。运行该测试时出现挂起现象,仅当先提交第一个事务再启动第二个事务时才不会挂起,请问该现象的原因是什么?当前使用的技术栈为Java 17、Spring Boot 3以及PostgreSQL数据库。
原因分析
1. PostgreSQL的行级排他锁机制
PostgreSQL执行UPDATE语句时,会对目标数据行添加排他锁(Exclusive Lock),该锁会持续持有至事务提交或回滚。在你的测试中,第一个事务执行balanceService.change后,已对balanceId=10000的行加锁,且此时第一个事务未提交,锁处于未释放状态。
2. 独立事务的锁阻塞
你为两个事务设置了PROPAGATION_REQUIRES_NEW传播行为,这会创建两个独立的物理事务(对应不同的数据库连接)。当第二个事务尝试更新同一行时,PostgreSQL会检测到该行已被其他事务持有排他锁,因此第二个事务会进入等待状态,直到锁被释放。
但你的代码逻辑是先启动两个事务并执行更新,再依次提交,这导致第二个事务等待第一个事务释放锁,而第一个事务需等待第二个事务执行完成才提交,形成循环等待,最终引发挂起。
3. 先提交第一个事务的可行原因
若调整逻辑为先提交第一个事务,再启动第二个事务,第一个事务提交后会立即释放排他锁,第二个事务执行UPDATE时可正常获取锁,不会出现阻塞,因此不会挂起。
内容的提问来源于stack exchange,提问作者dev-rifaii
相关产品推荐
相关产品推荐

