轉載:http://costlend.com/2016/03/14/dispatch-pay-balance-keep-consistence/
不管是電商,還是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;
通常情況下,這種余額判斷方法在高並發且不加鎖的情況下是非常不可靠的。所以在做扣費操作時要考慮到並發扣費的情況,允許讓其並發扣費,但是不應該允許賬戶余額為負數。轉賬的話也是一樣,相當於先從一個賬戶扣費,再給另一個賬戶充值,都必須要在一個事務內完成。
可以使用一個存儲過程來把這些步驟統一起來。下面的存儲過程親測可用。
1 create procedure proc_account_balance_dec ( in_money decimal(8,2), in_uid bigint, OUT status int ) 2 BEGIN 3 4 DECLARE from_account_balance decimal(8,2); 5 6 START TRANSACTION; 7 8 SELECT balance INTO from_account_balance FROM tb_account 9 WHERE uid = in_uid FOR UPDATE; 10 11 IF from_account_balance>=in_money THEN 12 UPDATE tb_account SET balance = balance - in_money , update_time = sysdate() 13 WHERE uid = in_uid; 14 COMMIT; 15 16 SET status=1; 17 ELSE 18 ROLLBACK; 19 SET status=0; 20 END IF;