我正在尝试编写springboot
代码以根据 DEBIT / CREDIT 交易更新钱包余额。
我有两张桌子,也就是。 钱包和交易。
我正在运行一个测试套件,该套件运行100个并行事务(50借方和50贷方)。大约50%的交易失败,并出现以下错误,并且钱包 表中的钱包余额与存储在交易中的交易不匹配< em>表
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());
}
答案 0 :(得分:0)
我在下一行使用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);
}
答案 1 :(得分:0)
如果您希望100%的即时一致性,可以探索功能锁定概念,将涉及一项交易的钱包保留在功能锁定表中。因此,开始时的每笔交易都会检查钱包中是否有任何功能锁,如果有,请等到它可以得到功能锁为止,否则将其插入功能锁中并继续进行交易。在功能锁表中添加条目应该是原子的,并具有自己的提交。在交易结束时,删除锁定条目。
如果您正在寻找最终的一致性(具有延迟的一致性),则可以考虑将事务更新排队,并且守护程序可以一个接一个地处理排队的项目,再次使用功能锁定概念来避免多个守护程序为事务处理事务。相同的钱包。