【发布时间】: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 需要接近零的时间来总结数千个数字。