简体   繁体   中英

Figure out database deadlock

I'm trying to write a springboot code to update the wallet balance based on the DEBIT/CREDIT transactions. I've two tables viz. wallet and transaction for that. I'm running a test suite which runs 100 parallel transactions (50 DEBIT and 50 CREDIT). About 50% transactions are failing with the following error, and also the wallet balance in wallet table is not matching the transactions stored in the transaction table

com.mysql.jdbc.exceptions.jdbc4.MySQLTransactionRollbackException: Deadlock found when trying to get lock; try restarting transaction

I am not able to figure out following 1 )Why Deadlock 2 )Why wallet balance is not matching the number of transactions successfully stored. I'm using MySQL InnoDB for database

@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());
  }

I fixed the deadlock by using findByIdInWriteMode instead of findById at the line below.

 Optional<Wallet> byId = walletRepo.findById(Integer.parseInt(walletId));

I changed my repo to

@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);
}

If you want 100% immediate consistency, you could explore the functional locking concept where you will keep the wallets involved in one transaction in a functional locking table. So every transaction at the beginning will check if there is any functional lock on the wallet, if so wait till it can get the functional lock otherwise insert into the functional lock and proceed with the transaction. Adding entries in functional lock table should be atomic with its own commit. And at the end of transaction, remove the locking entries.

If you are looking for eventual consistency(consistency with a delay), you can look to queue the transaction updates and daemons can process the queued items one by one, again using functional locking concept to avoid multiple daemons processing the transactions for the same wallet.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM