【场景】
电商类支付业务、金融类业务会经常存在这种账户之间的转账。
【设计】
举个例子,账户A往账户B转账500元。
这时候应该写一个transferMoney方法,入参是入账账户incomeAccount、出账账户outAccount、金额money。
1. 转账需要开启事务,START TRANSACTION
2. 入账账户SQL
update account_info set balance = balance + moneywhere account_id = incomeAccount.getId();
3. 出账账户SQL
update account_info set balance = balance - moneywhere account_id = incomeAccount.getId() and balance - money >= 0;
【思考】
- 上面仅仅是对账户之间的转账进行具体的操作,但是真正的场景设计,这种金额的增减并不能满足数据的安全。
2. 对于这种账户的转账操作,我们需要对所有的参与账户,要记录它们的流水,比如账户A增加500元的记录,账户B减少500元的记录,这样可以避免系统出现问题时,通过对账来判断账户是否出现数据不一致的问题。同时流水记录也可以作为对账系统的依据。
3. 针对某个账户使用update操作时,会出现行锁问题,如果是普通用户账户的操作,可能qps相对较低,不会影响太大。但是对于大卖家或者平台账户来说,很可能就会出现热点账户问题,这需要我们考虑性能问题,常见的业界做法就是拆分子账户、不进行实时更新余额、极致的性能优化输出。
- 在事务内,使用select ... for update来对查询记录加行锁,然后在程序逻辑上计算结果,最后使用update来更新值。这和上面这种update ... set balance = balance - money效果类同。
5. 是否需要考虑分布式问题??加分布式锁???我理解的是,在用户操作层面,由于不可控因素,导致产生了两次重复的请求,这种情况下是需要分布式锁和幂等设计来干预一下的。但是对于底层设计的转账逻辑,账户之间的转账会出现与多方进行的情况,这个数据库的锁已经起到保护作用。
【总结】
对于业务开发来说,这些看似简单的操作,却十分重要!!!
稍有不慎,对于转账类业务,就是一个资损的事故,哪怕没资损,也可能需要修复大量的脏数据,这可是程序员最讨厌的活!!!
简简单单,一起进步,一起成长!!
欢迎关注公众号:程序员的思考与落地