【问题标题】:Efficiently handle current balance of account有效处理当前账户余额
【发布时间】:2012-07-05 09:46:54
【问题描述】:

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

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

帐户 +----+-----------------+---------+------+ |编号 |当前余额 |版本 | ... | +----+-----------------+---------+------+ | 1 | 1000 | 48902 | ... | | 2 | 2000 | 34933 | ... | | 3 | 100 | 103 | ... | +----+-----------------+---------+------+ account_transaction +--------+-------------+----------+----- --+------------------+--+ |编号 | account_id |日期 |价值 |结果金额 | ... | +--------+-------------+----------+----- --+------------------+--+ | 101 | 1 | 2012 年 5 月 3 日 10:13:33 | 1000 | 2000 | ... | | 102 | 2 | 2012 年 5 月 3 日 10:13:33 | 500 | 1500 | ... | | 103 | 1 | 2012 年 5 月 3 日 10:13:34 | -500 | 1500 | ... | | 104 | 2 | 2012 年 5 月 3 日 10:13:35 | -50 | 1450 | ... | | 105 | 2 | 2012 年 5 月 3 日 10:13:35 | 550 | 2000 | ... | | 106 | 1 | 2012 年 5 月 3 日 10:13:35 | -500 | 1000 | ... | +--------+-------------+----------+----- --+------------------+--+

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

作为一个粗略的例子,在处理事务 102 时,应用程序执行了 以下伪 SQL/JAVA:

设置自动提交 = 0; 插入 account_transaction (account_id、日期、值、结果金额) 价值观 (2, sysdate(), 550, 2000); 更新帐户集 current_balance = 2000, 版本 = 34933 在哪里 id = 2 和 版本 = 34932; if (ROW_COUNT() != 1) { 回滚; } 别的 { 犯罪; }

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

如何有效地处理账户的当前余额?当前余额用于授权/拒绝新交易并用于各种报告。

【问题讨论】:

    标签: mysql performance database-design concurrency


    【解决方案1】:

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

    我认为整个模型都被过度设计了。

    通过version 放弃乐观锁定并拥有一个简单的...

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

    ...在事务的结束插入一个新的account_transaction 应该足够快。为了数据完整性,请考虑将其放入 account_transaction1 上的 AFTER INSERT 触发器中。

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

    1 但请注意不要过早触发它 - 仅在它完全“熟”时插入新的account_transaction,不要(例如)提前插入但更新resulting_amount 稍后。

    【讨论】:

    • 听起来不错,但有一个问题:resulting_amount 字段不会不正确吗?假设事务 X 和 Y 都读取 current_balance 并同时插入它们的 account_transaction 行。虽然在两次交易之后余额都是正确的,但是对于其中一行来说,结果金额不会是错误的吗?
    • @JoshNankin 我的回答集中在current_balance,如果您需要真正的“串行”resulting_amount,它就不会那么好用,正如您所指出的。尝试颠倒操作顺序:首先更新current_balance,然后将新余额复制到新的resulting_amount。 InnoDB 应该锁定更新,有效地序列化事务(在同一个account - 其他帐户可以同时继续)并避免插入异常。这仍然应该很快 - 我建议您在尝试实施自己的乐观锁定方案之前进行测量。
    猜你喜欢
    • 1970-01-01
    • 2016-09-23
    • 2018-01-12
    • 1970-01-01
    • 2011-12-09
    • 2016-10-11
    • 2021-05-19
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多