微信公眾號中(這里)看到一個關於MySQL的innodb_deadlock_detect與並發相關的細節,覺得比較有意思,也即innodb_deadlock_detect這個參數的設置問題
開始之前,關於鎖、死鎖,我們要先統一下幾點認知:
- 死鎖是由於多個事務相互持有對方所需要的鎖,結果導致事務都無法繼續,進而觸發死鎖檢測,其中某個事務會被回滾,釋放相應的鎖,其他事務得以正常繼續;簡言之,就是多個事務之間的鎖等待產生了回路,死循環了;
- 死鎖發生時,會立刻被檢測到,並且回滾某個事務,而不會長時間阻塞、等待;
- 從MySQL 5.7.15開始,新增選項 innodb_deadlock_detect,沒記錯的話應該是阿里率先實現的功能。當它設置為 OFF 時(默認值是 ON),InnoDB會不檢測死鎖,在高並發場景(例如“秒殺”)業務中特別有用,可以提高事務並發性能;
- 在啟用死鎖檢測時,InnoDB默認的最大檢測深度為200,在上面提到的高並發高競爭場景下,熱點數據上的鎖等待隊列可能很長,死鎖檢測代價很大。或者當等待隊列中所有的行鎖總數超過 100萬 時,也會認為發生死鎖,直接觸發死鎖檢測;
- InnoDB行鎖等待超時默認為50秒,一般建議設置5-10秒就夠了;
- 有時候,可能會口誤把 長時間的行鎖等待 說成是 死鎖,其實二者完全不一樣,不要犯這種常識性口誤。
死鎖檢測是一個MySQL Server層的自動檢測機制,可以及時發現兩個或者多個session間互斥資源的申請造成的死鎖,且會自動回滾一個(或多個)事物代價相對較小的session,讓執行代價最大的先執行。
該參數默認就是打開的,按理說也是必須要打開的,甚至在其他數據庫中沒有可以使其關閉的選項。
innodb_deadlock_detect
如果關閉innodb_deadlock_detect,也即關閉了死鎖自動監測機制時,當兩個或多個session間存在死鎖的情況下,MySQL怎么去處理?
這里會涉及到另外一個參數:鎖超時,也即innodb_lock_wait_timeout,該參數指定了“鎖申請時候的最長等待時間”
官方的解釋是:The length of time in seconds an InnoDB transaction waits for a row lock before giving up.
innodb_lock_wait_timeout默認值是50秒,也就是意味着session請求時,申請不到鎖的情況下最多等待50秒鍾,然后呢,就等價於死鎖,自動回滾當前事物了?其實不是的,事情沒有想象中的簡單。
innodb_rollback_on_timeout
這里就涉及到另外一個參數:innodb_rollback_on_timeout,默認值是off,該參數的決定了當前請求鎖超時之后,回滾的是整個事物,還是僅當前語句,
官方的解釋是:InnoDB rolls back only the last statement on a transaction timeout by default。
默認值是off,也就是回滾當前語句(放棄當前語句的鎖申請),有人強烈建議打開這個選項(on),也就是一旦鎖申請超時,就回滾整個事物。
需要注意的是,默認情況下只回滾當前語句,而不是整個事物,當前的事物還在繼續,連接也還在,這里與死鎖自動監測機制打開之后會主動犧牲一個事物不同,鎖超時后並不會主動犧牲其中任何一個事物。
這意味着會出現一種非常嚴重的情況,舉個例子,可以想象一下如下這種情況:
關閉了死鎖監測機制后,在innodb_rollback_on_timeout保持默認的off的情況下,session1和session2都是無法正常執行下去的,且永遠都無法執行下去。
任意一個session出現鎖超時,放棄當前的語句申請的鎖,而不是整個事物持有的鎖,當前session並不釋放其他session請求的鎖資源,
即便是繼續下去,依舊如此,兩者又陷入了相互等待,相互鎖請求超時,繼續死循環。
從這里可以看到,與死鎖自動檢測機制在發現死鎖是主動選擇一個作為犧牲品不同,一旦關閉了innodb_deadlock_detect,Session中的任意一方都不會主動釋放已經持有的鎖。
此時如果應用程序如果不足夠的健壯,繼續去申請鎖(比如重試機制,嘗試重試相關語句),session雙方會陷入到無限制的鎖超時死循環之中。
事實上推論是不是成立的?做個測試驗證一下,數據庫環境信息如下
模擬事物雙方在當前語句的鎖超時之后,繼續申請鎖,確實是會出現無限制的鎖超時的死循環之中。
以上就比較有意思了,與死鎖主動監測並犧牲其中一個事物不同,此時事物雙方互不相讓,當然也都無法成功執行。
這只不過是一個典型的負面場景,除此之外,還會有哪些問題值得思考?
1,因為事物無法快速提交或者回滾,那么連接持有的時間會增加,一旦並發量上來,連接數可能成為一個問題。
2,鎖超時時間肯定要設置為一個相對較小的時間,但具體又設置為多少靠譜。
3,關閉死鎖檢測,帶來的收益,與副作用相比哪個更高,當前業務類型是否需要關閉死鎖檢測,除非數據庫中相關操作大部分都是短小事物且所沖突的可能性較低。
4,面對鎖超時,應用程序端如何合理地處理鎖超時的情況,是重試還是放棄。
5,與此關聯的innodb_rollback_on_timeout如何設置,是保持默認的關閉(鎖超時的情況下,取消當前語句的所申請),還是打開(鎖超時的情況下,回滾整個事物)
在官方MYSQL 8.0 的文檔中提到在高並發的系統中還是建議不使用 innodb_deadlock_detect
大部分文字都在重復一個觀點,高並發使用死鎖的檢測,會引起性能的問題。
綜上所述可推薦關於死鎖以及事務回滾的MYSQL的配置:
1 innodb_deadlock_detect = off
2 innodb_lock_wait_timeout = 5
3 innodb_rollback_on_timeout = on
參考文章:
https://www.cnblogs.com/wy123/p/12724252.html