標籤:
情境如下: 使用者賬戶有餘額,當發生交易時,需要即時更新喻額。這裡如果發生並發問題,那麼會造成使用者餘額和實際交易的不一致,這對公司和客戶來說都是很危險的。那麼如何避免: 網上查了下,有以下兩種方法: 1、使用悲觀鎖 當需要變更餘額時,通過代碼在事務中對當前需要更新的記錄設定for update行鎖,然後開始正常的查詢和更新操作 這樣,其他的事務只能等待該事務完成後方可操作 當然要特別注意,如果使用了Spring的事務註解,需要配置一下:
<!-- (交易管理)transaction manager, use JtaTransactionManager for global tx --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource" /> </bean> <!-- 使用annotation定義事務 --> <tx:annotation-driven transaction-manager="transactionManager" />
在指定代碼處添加事務註解
@Transactional @Override public boolean increaseBalanceByLock(Long userId, BigDecimal amount) throws ValidateException { long time = System.currentTimeMillis(); //擷取對記錄的鎖定 UserBalance balance = userBalanceDao.getLock(userId); LOGGER.info("[lock] start. time: {}", time); if (null == balance) { throw new ValidateException( ValidateErrorCode.ERRORCODE_BALANCE_NOTEXIST, "user balance is not exist"); } boolean result = userBalanceDao.increaseBalanceByLock(balance, amount); long timeEnd = System.currentTimeMillis(); LOGGER.info("[lock] end. time: {}", timeEnd); return result; }
MyBatis中的鎖定方式,實際測試該方法確實可以有效控制,不過在大並發量的情況下,可能會有效能問題吧
<select id="getLock" resultMap="BaseResultMap" parameterType="java.lang.Long"> <![CDATA[ select * from user_balance where id=#{id,jdbcType=BIGINT} for update; ]]> </select>
2、使用樂觀鎖 這個方法也同樣可以解決情境中描述的問題(我認為比較適合并不頻繁的操作): 設計表的時候增加一個version(版本控制欄位),每次需要更新喻額的時候,先擷取對象,update的時候根據version和id為條件去更新,如果更新回來的數量為0,說明version已經變更 需要重複一次更新操作,如下:sql指令碼
update user_balance set Balance = #{balance,jdbcType=DECIMAL},Version = Version+1 where Id = #{id,jdbcType=BIGINT} and Version = #{version,jdbcType=BIGINT}
這是一種不使用資料庫鎖的方法,解決方式也很巧妙。當然,在大量並發的情況下,一次扣款需要重複多次的操作才能成功,還是有不足之處的。不知道還有沒有更好的方法。
Java如何?對Mysql資料庫的行鎖