不管是电商, 还是 O2O 业务都会涉及到支付, 而且多速情况下流量比较大, 尤其是在做活动的时候. 一般支付系统主要有充值, 扣费, 提现, 转账等功能, 那么在有些业务场景下, 尤其是多并发的情况下, 我们在做扣费业务操作时该怎样去保持账户余额的一致呢?
Java 开发人员可能第一个想法就是在调用扣减的 DAO 的方法上加上一个 synchronized 关键字, 这个解决办法在单节点应用部署是也许能生效管用, 但是在我们实际的应用场景中, 一般都是集群, 多节点部署的应用, 这个时候该如何解决呢?
我们有一张账户表 tb_account
field type desc
uid bigint 用户 id
balance decimal 余额
update_time datetime 表数据更新时间
扣费之前, 我们要先查询一下账户的余额是否足够抵扣, 然后再做真正的减扣.
大致的过程如下:
- select balance from tb_account where uid=100;
程序判断 balance 的值是否足够抵扣.
update tb_account set balance = balance - 28.00, update_time = sysdate() where uid=100;
通常情况下, 这种余额判断方法在高并发且不加锁的情况下是非常不可靠的. 所以在做扣费操作时要考虑到并发扣费的情况, 允许让其并发扣费, 但是不应该允许账户余额为负数. 转账的话也是一样, 相当于先从一个账户扣费, 再给另一个账户充值, 都必须要在一个事务内完成.
可以使用一个存储过程来把这些步骤统一起来. 下面的存储过程亲测可用.
- create procedure proc_account_balance_dec ( in_money decimal(8,2), in_uid bigint, OUT status int )
- BEGIN
- DECLARE from_account_balance decimal(8,2);
- START TRANSACTION;
- SELECT balance INTO from_account_balance FROM tb_account
- WHERE uid = in_uid FOR UPDATE;
- IF from_account_balance>=in_money THEN
- UPDATE tb_account SET balance = balance - in_money , update_time = sysdate()
- WHERE uid = in_uid;
- COMMIT;
- SET status=1;
- ELSE
- ROLLBACK;
- SET status=0;
- END IF;
来源: http://www.bubuko.com/infodetail-1977678.html