一次Mysql下批量更新造成的死鎖案例分析


  最近,公司現網的業務中出現上圖所示的死鎖異常,沿着問題分析,發現這個問題涉及很多數據庫的基礎知識。

 

背景:

  使用數據庫:Mysql

  涉及表格:t_invest

  數據庫隔離級別:可重復讀(Repeatable Read)

  死鎖場景:saveRepaymentInfo事務的A()方法對t_invest表執行如下update操作:

    <update id = "A" parameterType = "java.util.List">

      <foreach collecton = "list" item = "item" separator = ";">

        update t_invest

          set status = 1,

          end_date = #{item.endDate},

          period = #{item.periods},

          update_time = now()

          where invest_no = #{item.invest_no}

      </foreach>

      </update>

      invest_no字段為設置了唯一索引

異常信息:

 

死鎖分析:

通過異常日志跟蹤定位到業務代碼,發現死鎖出現在syncRedeemApply事務。

事務syncRedeemApply會進行t_invest表status的批量更新,批次最大數量為1000條。t_invest表的事務隔離級別為“可重復讀”,在該隔離級別下,每次執行更新操作時會對索引加行鎖,這個事務不存在多線程並發訪問的情況,推斷不是因為多程序並發操作造成的死鎖。

通過分析業務功能發現,其他分布式業務模塊在saveRepaymentInfo事務進行t_invest表的status的批量更新的同時,另外有一個updateExitStatusByBatch事務同時也在invest表進行批量更新,而且都是利用investno索引進行批量update,問題就出現在這里!

updateExitStatusByBatch事務進行批量更新的方法假設是B(),其批量更新語句為:

  <update id = "B" parameterType = "java.util.List">

    <foreach collecton = "list" item = "item" separator = ";">

      update t_invest

        set status = 1,

        end_date = #{item.endDate},

        period = #{item.periods},

        update_time = now()

        where invest_no = #{item.invest_no}

    </foreach>

  </update>

(其實兩個事務的方法A與B的sql是一樣的)

 死鎖是因為兩個或兩個以上的線程在執行過程中,由於競爭資源或者由於彼此通信而造成的一種阻塞的現象,若無外力作用,它們都將無法推進下去。此時稱系統處於死鎖狀態或系統產生了死鎖,這些永遠在互相等待的進程稱為死鎖進程。

 如下圖所示:

t_fixin_invest的事務隔離級別為“可重復讀”,每次執行更新操作時會對索引加行鎖,在一個事務內批量修改如果沒有全部修改完,索引是不會被放開的,亦即索引上的行鎖不會被放開,所以當兩個事務中同時進行更新時,如果有重復數據,有可能出現互相等待,從而爆死鎖。

如上圖,syncRedeemApply事務鎖住i1、i3、i5,updateExitStatusByBatch事務鎖住i2、i4、i6,syncRedeemApply事務執行到i2的update,等待updateExitStatusByBatch事務釋放i2的鎖,updateExitStatusByBatch事務執行到i1的updat,等待syncRedeemApply事務釋放i1的鎖,如此,兩個事務互相等待,造成死鎖。

我的解決辦法是這兩個事務在進行批量插入操作,先按主鍵進行排序,避免在插入時有重復數據出現互相等待,然后再進行批量插入操作!


免責聲明!

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



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