一次MySQL死鎖記錄

一次被自己蠢到的數據庫死鎖經歷

先說這次事件的背景故事,一個創業項目,需要冷啓動。該項目類似於微博的一個項目,所以冷啓動需要導入一批微博數據和微博評論數據,導入數據還需要在馬甲賬號加上統計數據,因爲可以查看他人中心,不然太假。就在更新馬甲賬號統計數據的時候老是發生死鎖。

技術背景,我開一個接口給爬蟲工程師上傳約定的標準json文件,由於考慮數據可能會比較多,所以用了一個線程池去插入數據,線程池大小16。僞代碼如下:

  1. Service類:
List<D> dataList = Json.toObj(json);
dataList.forEach(
    v->{
      threadPoolTaskExecutor.execute(() -> {
                      dataVHandler.handler(v);
              });  
    }
)
  1. Handler類:
  @Transactional
  void method(data){
      weiboRepository.save(data);
  	List<C> commentList = data.getCommentList();
      commentList.forEach(c->{
          commentRepository.save(c);
          roleDataRepository.update(c.getUserId(), numData);
      })
      roleDataRepository.update(data.getUserId(), numData);
  }
  1. UserDataRepository類:
@Transactional
void update(userId, numData){
   D userData = selectByUserId(userId);
    if(null == userData){
        // 初始化且合併數據
        merge(userData,numData);
        save(userData);
    }
    // 合併數據
    merge(userData,numData);
    update(userData);
}

一開始,總看在後面這個小事物中,後來想想,userId未加索引,select 會掃描全表,但是select也沒有加上加鎖關鍵字,也不應該啊,爲了效率和防止出錯,給userId加上了索引,還有我數據事物隔離級別是讀提交,沒有間隙鎖,行鎖也不應該死鎖啊。結果當然是依舊死鎖。

各種分析,使用數據庫命令,查看死鎖結果,死鎖結果都是鎖記錄鎖(行鎖,這也說明不是間隙鎖),並且是兩個記錄之間互相等待死鎖,想了很久都沒想明白爲什麼。

於是問別人,其他人也不知道爲啥,但是得到了新的思路,有人提出了,可能更新連接不夠,Mybaits框架也會出現問題,報鎖等待超時異常。結果測試增加連接池,依據死鎖。

總想啊,到晚上睡覺前都沒找到原因,第二天到公司,繼續找原因,於是我一直導入同個數據,發現總是卡在某個數據,肯定是導入的時候文件中那行數據有問題,於是拿出來分析一下,瞬間腦子清醒了。爲啥呢?因爲兩條記錄的userId順序互相交叉了,一條微博下面有個評論列表,評論列表也有馬甲賬號id。然後再查一下Spring的事物傳播特性,默認是合併事物,所以上面代碼的事物是在handler的事物中執行,handler的事物更新了多個UserData,然後併發事物交叉更新,就死鎖了。最後在UserDataRepository類另起一個事物,最後測試一下,沒有死鎖OK了。修改後代碼如下:

@Transactional(propagation = Propagation.REQUIRES_NEW)
void update(userId, numData){
   D userData = selectByUserId(userId);
    if(null == userData){
        // 初始化且合併數據
        merge(userData,numData);
        save(userData);
    }
    // 合併數據
    merge(userData,numData);
    update(userData);
}

死鎖原因(普通的交叉):

在這裏插入圖片描述

總結

在一個事物中多次更新同一個表的時候,一定要特別注意,因爲多次更新同一個表,就可能會出現交叉的情況,就會發生死鎖。

【參考】:
GC Ergonomics間接引發的鎖等待超時問題排查分析
記spring事務傳播機制引發連接池死鎖問題及解決方案
Mybatis-update - 數據庫死鎖 - 獲取數據庫連接池等待
Mybatis-update - 數據庫死鎖 - 獲取數據庫連接池等待

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章