有效地处理当前的账户余额

时间:2012-07-05 00:47:27

标签: mysql performance database-design concurrency

我一直在开发一个处理这些帐户的帐户和交易的应用程序。

目前,应用程序使用的表格按以下方式建模:

account
+----+-----------------+---------+-----+
| id | current_balance | version | ... |
+----+-----------------+---------+-----+
| 1  | 1000            | 48902   | ... |
| 2  | 2000            | 34933   | ... |
| 3  | 100             | 103     | ... |
+----+-----------------+---------+-----+

account_transaction
+------+-------------+----------------------+---------+------------------+-----+
|  id  | account_id  |          date        |  value  | resulting_amount | ... |
+------+-------------+----------------------+---------+------------------+-----+
| 101  | 1           | 03/may/2012 10:13:33 | 1000    | 2000             | ... |
| 102  | 2           | 03/may/2012 10:13:33 | 500     | 1500             | ... |
| 103  | 1           | 03/may/2012 10:13:34 | -500    | 1500             | ... |
| 104  | 2           | 03/may/2012 10:13:35 | -50     | 1450             | ... |
| 105  | 2           | 03/may/2012 10:13:35 | 550     | 2000             | ... |
| 106  | 1           | 03/may/2012 10:13:35 | -500    | 1000             | ... |
+------+-------------+----------------------+---------+------------------+-----+

每当应用程序处理新事务时,它会在 account_transaction 中插入一个新行,并在 account 表中更新列 current_balance 存储帐户的当前余额和用于乐观锁定的列版本。 如果乐观锁定有效,则提交事务,如果事务没有回滚。

作为一个粗略的例子,当处理事务102时,应用程序完成了 跟随伪SQL / JAVA:

set autocommit = 0;

insert into account_transaction
(account_id, date, value, resulting_amount)
values
(2, sysdate(), 550, 2000);

update account set 
    current_balance = 2000, 
    version = 34933  
where 
    id = 2 and
    version = 34932;

if (ROW_COUNT() != 1) {
    rollback;
}
else {
    commit;
}

但是,某些帐户非常活跃并且会收到许多同时发生的事务,这会导致MySQL在更新 account 表中的行时出现死锁。这些死锁会对应用程序造成严重的性能损失,因为它会导致在数据库发生死锁时重新处理事务。

如何有效处理帐户的当前余额?授权/拒绝新交易需要当前余额,并在各种报告中使用。

1 个答案:

答案 0 :(得分:3)

  

如何有效处理帐户的当前余额?

我认为整个模型都是过度设计的。

通过version放弃乐观锁定并简单地......

UPDATE account SET current_balance = current_balance + value WHERE id = ...

...在插入新account_transaction的事务的 end 应该足够快。为了数据完整性,请考虑将此项置于account_transaction 1 上的AFTER INSERT触发器中。

  • 首先,您是在事务结束时进行的,因此即使事务很长,此行上的锁争用也应该很短。
  • SQL保证在单个语句中保持一致的数据视图,因此不需要单独的SELECT ... FOR UPDATE
  • 此外,由于您添加一个值,而不是直接设置总和,这些操作的执行顺序并不重要 - 添加是可交换的(因此更短的事务可以"超过"更长的那些)。

1 但要注意不要过早触发它 - 只有在完全"煮熟时才插入新的account_transaction,并且不要(例如)早期插入,但稍后更新resulting_amount