我正在尝试编写一个springboot代码来更新基于借方/信用事务的钱包余额。我有两张桌子。 that 和transaction就是为了实现这一目标。我正在运行一个测试套件,它运行100个并行事务(50个借方和50个信用)。大约50%的事务失败,出现以下错误,而且钱包表中的钱包余额与存储在transaction表中的事务不匹配
尝试锁定时发现com.mysql.jdbc.exceptions.jdbc4.MySQLTransactionRollbackException:死锁;尝试重新启动事务
我无法搞清楚:1)为什么死锁,2)为什么钱包余额与成功存储的事务数不匹配。我使用MySQL InnoDB作为数据库
@Transactional(isolation = Isolation.SERIALIZABLE)
public Transaction saveTransaction(String walletId, Txn txn) {
Optional<Wallet> byId = walletRepo.findById(Integer.parseInt(walletId));
if (!byId.isPresent()) {
throw new WalletNotFoundException();
}
Wallet wallet = byId.get();
Transaction transaction = new Transaction();
transaction.setAmount(txn.getAmount());
transaction.setType(txn.getType());
transaction.setWallet(wallet);
BigDecimal balance = applyTransactionToWallet(txn, wallet.getBalance());
Transaction save = transactionRepo.save(transaction);
wallet.setBalance(balance);
return save;
}
public Optional<Wallet> getWallet(String walletId) {
Optional<Wallet> byId = walletRepo.findById(Integer.parseInt(walletId));
return byId;
}
private BigDecimal applyTransactionToWallet(Txn txn, BigDecimal amount) {
if (txn.getType() == TransactionType.CREDIT) {
return amount.add(txn.getAmount());
}
return amount.subtract(txn.getAmount());
}发布于 2018-10-16 18:21:32
我在下面的行中使用findByIdInWriteMode而不是findById来修复死锁。
Optional<Wallet> byId = walletRepo.findById(Integer.parseInt(walletId));我把我的回购改为
@Repository
public interface WalletRepo extends JpaRepository<Wallet, Integer> {
@Query(value = "FROM Wallet W where W.walletId = :id")
@Lock(LockModeType.PESSIMISTIC_WRITE)
Optional<Wallet> findByIdInWriteMode(@Param("id") Integer id);
}发布于 2018-10-16 18:46:35
如果您想要100%的即时一致性,您可以探索功能性锁定概念,其中您将把涉及到一个事务的钱包保存在一个功能锁定表中。因此,开始时的每个事务都会检查钱包上是否有任何功能性锁,如果是的话,等到它能够获得功能锁,否则插入到功能锁中,然后继续处理。在functional表中添加条目应该是原子的,并具有自己的commit。在事务结束时,删除锁定项。
如果您正在寻找最终的一致性(与延迟的一致性),您可以查看事务更新的队列,守护进程可以一个接一个地处理排队的项,再次使用函数锁定概念,以避免多个守护进程处理同一个钱包的事务。
https://stackoverflow.com/questions/52820374
复制相似问题