下面通過壹個例子來說明
場景如下:
用戶賬戶有余額,當發生交易時,需要實時更新余額。這裏如果發生並發問題,那麽會造成用戶余額和實際交易的不壹致,這對公司和客戶來說都是很危險的。
那麽如何避免:
網上查了下,有以下兩種方法:
1、使用悲觀鎖
當需要變更余額時,通過代碼在事務中對當前需要更新的記錄設置forupdate行鎖,然後開始正常的查詢和更新操作
這樣,其他的事務只能等待該事務完成後方可操作
當然要特別註意,如果使用了Spring的事務註解,需要配置壹下:
class="org..jdbc.datasource."
在指定代碼處添加事務註解
@
@Override
publicboolean(LonguserId,BigDecimalamount)
throws{
longtime=System.();
//獲取對記錄的鎖定
UserBalancebalance=.getLock(userId);
LOGGER.info("[lock]start.time:{}",time);
if(null==balance){
thrownew(
.ERRORCODE_BALANCE_NOTEXIST,"userbalanceisnotexist");
}
booleanresult=.(balance,amount);
longtimeEnd=System.();
LOGGER.info("[lock]end.time:{}",timeEnd);
returnresult;
}
MyBatis中的鎖定方式,實際測試該方法確實可以有效控制,不過在大並發量的情況下,可能會有性能問題吧
select*fromuser_balancewhereid=#{id,jdbcType=BIGINT}forupdate;
]]
2、使用樂觀鎖
這個方法也同樣可以解決場景中描述的問題(我認為比較適合並不頻繁的操作):
設計表的時候增加壹個version(版本控制字段),每次需要更新余額的時候,先獲取對象,update的時候根據version和id為條件去更新,如果更新回來的數量為0,說明version已經變更
需要重復壹次更新操作,如下:sql腳本
updateuser_balancesetBalance=#{balance,jdbcType=DECIMAL},Version=Version1whereId=#{id,jdbcType=BIGINT}andVersion=#{version,jdbcType=BIGINT}
這是壹種不使用數據庫鎖的方法,解決方式也很巧妙。當然,在大量並發的情況下,壹次扣款需要重復多次的操作才能成功,還是有不足之處的。不知道還有沒有更好的方法。