如何在每次交易后显示总金额?该总金额应采用什么方式存储?
虚拟币交易记录累计余额展示方案对比
方案1:新增TotalAmount字段预存累计余额
- 优势:查询时直接返回字段即可,无额外计算开销,不管是分页查询还是全量导出交易记录性能都拉满,也不需要处理分页场景下的跨页累计逻辑。
- 并发问题解决方案:可通过数据库原子操作完全规避,不需要在应用层做复杂的并发控制。以MySQL为例,插入交易时可以用带行锁的
INSERT ... SELECT原子语句实现累计余额计算:
INSERT INTO transaction (UserId, ChangeInBalance, Created, TotalAmount) SELECT 5, -3, NOW(), COALESCE(MAX(TotalAmount), 0) + (-3) FROM transaction WHERE UserId = 5 FOR UPDATE;
上述语句会先锁定对应用户的所有交易记录计算最新余额,再执行插入,全程原子性,不会出现并发插入导致的余额计算错误。
- 适用场景:交易量大、查询频率高、对查询性能要求严苛的生产级金融业务场景。
方案2:实时计算累计余额
分为两种实现方式,均不需要修改现有表结构:
- 数据库层用窗口函数计算(MySQL 8.0+/PostgreSQL均支持),查询时直接生成累计余额:
SELECT Id, UserId, ChangeInBalance, Created, SUM(ChangeInBalance) OVER (PARTITION BY UserId ORDER BY Created ASC, Id ASC) AS TotalAmount FROM transaction WHERE UserId = 5 ORDER BY Created ASC, Id ASC LIMIT 0,10;
- 应用层计算:分页查询到当前页的交易列表后,先查询当前页第一条交易之前的所有余额变动总和作为初始值,再遍历当前页的每条记录累加得到每条的累计余额。
- 优势:无数据冗余,不需要处理插入时的并发逻辑,开发成本极低,单用户交易记录在10万条以内、单页查询条数不超过100条时性能完全够用。
- 劣势:当单用户交易记录超过10万条时,窗口函数计算耗时会明显上升,应用层计算也需要多一次历史余额查询的IO开销。
其他可选方案
如果已经做了冷热数据分离,可以把归档的冷交易记录的累计余额直接固化存储,近3个月的热数据用实时计算的方式,兼顾性能和存储成本,没有特殊需求不建议采用,会额外增加系统复杂度。
选型建议
- 交易插入QPS低于1000、单用户交易记录普遍在10万条以内:优先选窗口函数实时计算的方案,不需要改表,逻辑最简洁
- 交易查询频率极高、单用户交易记录普遍超过10万条:选预存
TotalAmount的方案,用原子插入语句即可解决并发问题,是目前主流金融系统的通用实现
内容的提问来源于stack exchange,提问作者Vladyslav Kalashnikov
相关产品推荐
相关产品推荐

