樂觀鎖和悲觀鎖


場景如下:
    用戶賬戶有余額,當發生交易時,需要實時更新余額。這里如果發生並發問題,那么會造成用戶余額和實際交易的不一致,這對公司和客戶來說都是很危險的。
那么如何避免, 有以下兩種方法:
    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;
    }

通過db的悲觀鎖,實際測試該方法確實可以有效控制,不過在大並發量的情況下,可能會有性能問題

  <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}

 這是一種不使用數據庫鎖的方法,解決方式也很巧妙。當然,在大量並發的情況下,一次扣款需要重復多次的操作才能成功,還是有不足之處的。

 

延伸:

樂觀鎖:對每次的數據操作都保持樂觀的態度,因此不對數據進行上鎖。那么就存在數據會被反復讀寫的情況,所以每次修改數據的時候需要對數據進行判斷是否被修改過。

悲觀鎖:對每次的數據操作持悲觀態度,操作時上鎖,防止操作時數據被他人修改

使用場景:

樂觀鎖:由於不上鎖,性能較好,適用於讀大於寫的情況,如果寫較多,則會導致重復嘗試寫入均失敗。

悲觀鎖:上鎖,數據寫入時會導致讀被掛起,適合寫大於讀的場景

實現:

樂觀鎖:在db中,可以增加版本號控制,java中的CAS等

悲觀鎖:db的for update,java中的synchronize等

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM