【问题标题】:Figure out database deadlock找出数据库死锁
【发布时间】:2018-10-15 15:46:50
【问题描述】:

我正在尝试编写springboot 代码来根据DEBIT/CREDIT 交易更新钱包余额。 我有两张桌子,即。 钱包交易。 我正在运行一个运行 100 个并行事务(50 个借项和 50 个贷项)的测试套件。大约 50% 的交易因以下错误而失败,并且 wallet table 中的钱包余额与 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());
  }

【问题讨论】:

  • 这真的不是处理任何财务问题的好方法。总是,总是使用分类帐来记录交易。如果设计得当,那么交易表上的SUM(balance) WHERE wallet_id=? 将为您提供任何给定钱包的当前余额。您在这里所拥有的似乎有一个没有分类帐的单数 balance 列,这意味着您不知道余额是如何产生的。
  • 这只是一个实践项目,而不是一个实际的应用程序。
  • 正是您应该学习如何正确操作的原因。这并不难,而且它避免了您遇到的锁定问题,因为分类帐基本上是仅追加的,没有记录级别的争用。
  • 但是,假设一个钱包的账本中有太多条目,那么对于每个读取请求,我都必须评估账本,这可能很昂贵
  • 使用正确的索引通常非常快,即使有“很多”条目。 MySQL 需要接近零的时间来总结数千个数字。

标签: java mysql sql innodb


【解决方案1】:

我在下面的行中使用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);
}

【讨论】:

    【解决方案2】:

    如果您想要 100% 的即时一致性,您可以探索功能锁定概念,您将在功能锁定表中保留参与一项交易的钱包。所以每笔交易一开始都会检查钱包上是否有任何功能锁,如果有则等到它可以得到功能锁,否则插入功能锁并继续交易。在功能锁表中添加条目应该是原子的,具有自己的提交。并在事务结束时,删除锁定条目。

    如果您正在寻找最终一致性(有延迟的一致性),您可以查看将事务更新排队,并且守护进程可以一个一个地处理排队的项目,再次使用功能锁定概念来避免多个守护进程处理事务同一个钱包。

    【讨论】:

      猜你喜欢
      • 2012-09-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多