業務新上了一個功能,在發布的過程中,系統報出了數據庫死鎖異常:
com.mysql.jdbc.exceptions.jdbc4.MySQLTransactionRollbackException: Deadlock found when trying to get lock; try restarting transaction
死鎖發生在一個事務中,事務對多個表進行了操作。在報錯日志中,死鎖發生在tableA與tableB。一開始懷疑此次發布的某個改動中對上面這兩張表新增了select或update操作。將注意力用在排查這個問題上。排查后發現沒有相關的變更,又猜測是否是由於更改造成並發請求進來,接口原來是有加分布式鎖的,需求更改中縮小了分布式鎖的粒度,確實是有可能造成並發請求。但很快又否定了,秒殺場景下的並發量尚且不會發生死鎖,何況是這個接口?覺得問題應該別有所在。先回滾了需求后,聯系dba執行了命令SHOW ENGINE INNODB STATUS
將死鎖日志拉取了出來:
從死鎖日志可以看到事務(1)TRANSACTION
嘗試更新表A,等待表A的鎖(1)WATING FOR THIS LOCK TO BE GRANTED
.但此時另外一個事務(2)TRANSACTION
已經持有了表A的鎖:(2)HOLDS THE LOCK(S)
,同時也在等待表B的鎖(2)WATING FOR THIS LOCK TO BE GRANTED
. 情況可能如下所示:
事務(1) | 事務(2) |
---|---|
持有表1的寫鎖,並更新了表1 | |
等待表1的寫鎖 | |
等待表2的寫鎖 |
由於事務2一直都獲取不到表2的寫鎖,事務2無法提交,因此事務2持有的表1鎖沒有釋放(在事務執行過程中,如果有加鎖操作,這個鎖需要等事務提交時釋放),導致事務1一直在等待表1的寫鎖,從而最終導致死鎖。那么表2的寫鎖被哪個事務持有了?有沒有可能是事務1?也即是下面這種情況:
事務(1) | 事務(2) |
---|---|
持有表2的寫鎖,並更新了表2 | |
持有表1的寫鎖,並更新了表1 | |
等待表1的寫鎖 | |
等待表2的寫鎖 |
由於更新的是同一個用戶的同一行記錄,這種情況可能在sql執行順序不一致所導致,所以對比了需求變更前后的事務邏輯,果然發現了端倪:
變更前:
事務開始
「
更新表1
更新表2
」
事務提交
變更后:
事務開始
「
更新表2
更新表1
」
事務提交
在發布的過程中,有部分機器的代碼處於變更前,有部分機器的代碼處於變更后,最終導致了上述的死鎖問題。此時原因已經明了,但是疑惑的是為什么死鎖出現時馬上就報錯,不會進行等待?查閱文檔發現:死鎖檢測開了后,發生死鎖會立馬回滾。死鎖檢測關閉,鎖等待超時時間這個設置才會生效: