Выяснить тупик базы данных - PullRequest
0 голосов
/ 15 октября 2018

Я пытаюсь написать springboot код для обновления баланса кошелька на основе транзакций DEBIT / CREDIT .У меня есть две таблицы, а именно. кошелек и транзакция для этого.Я использую набор тестов, который выполняет 100 параллельных транзакций (50 дебетов и 50 кредитов).Около 50% транзакций не выполняются со следующей ошибкой, а также баланс кошелька в таблице кошелек не соответствует транзакциям, хранящимся в таблице транзакция

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

Ответы [ 2 ]

0 голосов
/ 16 октября 2018

Если вы хотите 100% немедленной согласованности, вы можете изучить концепцию функциональной блокировки, где вы будете хранить кошельки, участвующие в одной транзакции, в таблице функциональной блокировки.Таким образом, каждая транзакция в начале будет проверять, есть ли функциональная блокировка на кошельке, если это так, подождите, пока она не получит функциональную блокировку, в противном случае вставьте в функциональную блокировку и продолжите транзакцию.Добавление записей в таблицу функциональных блокировок должно быть атомарным со своим собственным коммитом.И в конце транзакции удалите блокирующие записи.

Если вы ищете возможную согласованность (согласованность с задержкой), вы можете посмотреть в очереди обновления транзакций, и демоны могут обрабатывать поставленные в очередь элементы один за другим.снова используя концепцию функциональной блокировки, чтобы избежать того, что несколько демонов обрабатывают транзакции для одного и того же кошелька.

0 голосов
/ 16 октября 2018

Я исправил тупик, используя 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);
}
...