記一次mysql死鎖問題

這是我第一次遇到mysql死鎖問題,通過這次死鎖問題的分析,我對數據庫的鎖和有了更詳細的認識。漫漫長路,一點點走。

這是我的demo復現,出現的原因是在事務裏面循環更新數據,注意,此時我的mysql默認事務隔離級別是提交讀,且name和sex添加了組合索引。

public void test() {
    User user = new User("a", "a", "aa");
    User user1 = new User("b", "b", "bb");
    User user2 = new User("c", "c", "cc");
    List<User> list1 = new ArrayList();
    list1.add(user);
    list1.add(user1);
    List<User> list2 = new ArrayList();
    list2.add(user2);
    list2.add(user1);
    list2.add(user);
    new Thread(() ->{
        doTranscation(list2);
    }).start();
  doTranscation(list1);

}

public void doTranscation(List<User> list) {
    Object execute = transactionTemplate.execute(transactionStatus -> {
        list.forEach(u ->mapper.updateByNameAndSex(u));
        return true;
    });
}

sql:

update user
set  mark = #{mark}
where name = #{name} and sex = #{sex}

代碼可以不看,聽我分析。

在一個事務中,我循環更新了幾條符合條件的記錄,另一個事務中,也更新了幾條符合條件的記錄。如果這幾個記錄存在重合(而且重合的記錄必須大於等於2),且假如這兩個事務同時提交的話,那就很可能會出現死鎖。

爲什麼呢?

因爲一個事務會鎖住所有符合條件的記錄(X鎖,排他鎖),只有當事務執行完畢纔會釋放鎖(要不然整個事務怎麼回滾?)。

此時一個事務A如果按順序鎖住a和b,同時另一個事務B要按順序鎖住b和a,當A鎖住a,B鎖住b,此時A要鎖b,發現b被B鎖住了,於是等到B執行完。B要鎖a,發現 a被A鎖住了,於是等A執行完。那會發生什麼,倆事務都在這傻等?這時候mysql就會爆出異常,deadLock了。

那麼怎麼防止這種死鎖呢?

你可以把循環拿到事務外面,這樣 每次一個事務就只能鎖某個符合條件的記錄了,從而大大降低了死鎖的風險(減少了每個事務鎖住的記錄)。但是如果你的sql語句where條件命中多條符合記錄的話,這樣一個事務鎖的還是多條數據,雖然降低了風險,但是仍然有死鎖的可能。(其實這樣也相當於改了業務邏輯,一旦異常,回滾的就不是所有的更新,而是發生異常的更新了)。

別的方法。就只能改業務邏輯了。如果還有什麼方法能優化的話,歡迎提出。

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