這是我第一次遇到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條件命中多條符合記錄的話,這樣一個事務鎖的還是多條數據,雖然降低了風險,但是仍然有死鎖的可能。(其實這樣也相當於改了業務邏輯,一旦異常,回滾的就不是所有的更新,而是發生異常的更新了)。
別的方法。就只能改業務邏輯了。如果還有什麼方法能優化的話,歡迎提出。