場景:
業務邏輯:第三方登錄情況下,獲取到用戶的實名信息。之後判斷該用戶在用戶表中是否存在,如果不存在或非實名,那麼將其實名;如果已經實名,那麼不做處理,直接登錄。ORM使用的是spring data jpa,用戶表在mobile字段上有唯一索引idx_mobile
發現不定期的發生業務報錯:Deadlock found when trying to get lock; try restarting transaction
原因:
分析死鎖日誌
通過SHOW ENGINE INNODB STATUS;來查看死鎖日誌:
日誌類似
注意:SHOW ENGINE INNODB STATUS\G 看到的DEADLOCK相關信息,只會返回最後的2個事務的信息,而其實有可能有更多的事務才最終導致的死鎖
日誌的上半部分說明事務1在等待什麼鎖
ip1 dbuser update
這個用戶在執行下面這條sql語句
insert into 用戶表 值1
其在申請idx_mobile索引的
RECORD LOCKS space id 3251 page no 14336 n bits 704 index `idx_mobile` of table 用戶表 trx id 306872608 lock_mode X locks gap before rec insert intention waiting
這條插入記錄的事務等待中,等待獲得插入意向鎖
日誌的下半部分說明了事務2當前持有的鎖以及等待的鎖:
ip2 dbuser update
這個用戶在執行下面這條sql語句
insert into 用戶表 也是值1
HOLDS THE LOCK(S):
事務2持有S gap lock
lock mode S locks gap before rec
至於爲什麼加S Gap-Lock ,是因爲在插入之前還需要多一步檢查:如果記錄中有唯一約束,判斷存在一條記錄等於當前插入的記錄時,則需要在這個記錄加上S Gap-Lock
也就是說事務1的insert intention lock等待事務2的s gap-lock釋放
從日誌的WAITING FOR THIS LOCK TO BE GRANTED塊中我們可以看到事務2正在申請插入意向鎖
那是什麼原因造成這個dead lock呢?
是事務0的回滾導致事務1和事務2的deadlock
爲什麼是事務0呢,看後面的參考就知道了,事務1和事務2的死鎖是由於事務0的rollback導致的
參考:
還原整個過程
第三方登錄的情況下,前後端沒有做重複提交的避免策略,這樣會造成一個用戶可以多次執行第三方登錄的請求,當用戶短時間內連續3次(或以上)執行第三方登錄的請求,導致會起3個transaction(事務0 事務1 事務2)去執行insert操作
此時如果事務0由於業務代碼問題rollback,會導致事務1和事務2 deadlock,直到mysql鎖超時,報deadlock錯誤
即發生:當有3個(或以上)事務對相同的表進行insert操作,如果insert對應的字段上有uniq key約束並且第一個事務rollback了,那其中一個將返回死鎖錯誤信息。
解決方案
避免此DEADLOCK;我們都知道死鎖的問題通常都是業務處理的邏輯造成的,既然是uniq key,同時多臺不同服務器上的相同程序對其insert一模一樣的value,這本身邏輯就不太完美。故解決此問題:
思路1:
保證業務程序別在同一時間點併發的插入相同的值到相同的uniq key的表中
前端可以通過
1.提交數據之前判斷當前提交按鈕是否存在lock鎖
2.在ajax提交之前給提交按鈕上鎖
3.ajax成功之後或者失敗之後解鎖
後端可以通過redis+aop來做
參考:redis防表單重複提交
思路2:
由於是事務0 rollback了才產生的deadlock,查明rollback的原因
我們的解決方法
我們現在是前端做重複提交的去重。
後端修改了可能產生rollback的邏輯