鎖等待之後有兩種結果:獲得鎖、超時,這一期先來看看鎖等待超時之後都要幹什麼?
作者:操盛春,愛可生技術專家,公眾號『一樹一溪』作者,專注於研究 MySQL 和 OceanBase 源碼。
愛可生開源社區出品,原創內容未經授權不得隨意使用,轉載請聯繫小編並註明來源。
本文基於 MySQL 8.0.32 源碼,存儲引擎為 InnoDB。
正文
1. 超時檢查線程
InnoDB 有個名為 ib_srv_lock_to 的後台線程,每秒進行一次超時檢查,看看是否有鎖等待超時的事務。
前面介紹鎖等待時,我們介紹過:如果事務加鎖進入鎖等待狀態,會給後台線程發送通知,告訴後台線程發生了鎖等待,這個後台線程就是超時檢查線程。
既然每個事務進入鎖等待狀態都會通知超時檢查線程,那麼,每秒進行一次超時檢查的説法是不是有問題?
這自然是沒問題的了。
因為超時檢查線程是個多面手,它不只會進行超時檢查,還會做別的事情,那就是死鎖檢查。
事務進入鎖等待狀態時,給超時檢查線程發送通知,是為了觸發這個線程馬上進行死鎖檢查。
收到通知之後,超時檢查線程會判斷距離上一次超時檢查的時間。如果小於 1s,就不進行超時檢查,大於等於 1s 才會進行超時檢查。
2. 找到超時事務
超時檢查,是為了找到那些鎖等待超時的事務。處於鎖等待狀態的事務有可能很多,怎麼能快速找到哪些事務已經等到花兒都謝了?
鎖模塊結構有個 waiting_threads 屬性,是個指針,指向一片內存區域。
這片內存區域有 srv_max_n_threads 個 slot,每個 slot 存放一個 srv_slot_t 對象。
srv_slot_t 對象中保存着事務對象、開始等待時間、超時時間等相關信息。
鎖模塊結構還有個 last_slot 屬性,也是個指針,和 waiting_threads 指向同一片內存區域,但是它指向的不是這片內存區域的開始處,而是已被使用的最後一個 slot 後面的那個 slot。
也就是説,last_slot 指向的那個 slot 和後面所有的 slot 都是空閒狀態。
上圖中已被使用的最後一個 slot 是 srv_slot_t 7,last_slot 指向它後面的那個 slot,也就是 srv_slot_t 8。
另外,從上圖中我們也可以看到 waiting_threads 和 last_slot 之間,並不是所有 slot 都被使用了,也會有空閒的。
對 waiting_threads、last_slot 指向的內存區域有所瞭解之後,我們就可以更好的欣賞超時檢查線程的表演了。
找到鎖等待超時的事務,需要遍歷 waiting_threads 指向的內存區域中的 slot,從第一個 slot 開始,到已被使用的最後一個 slot 為止。
已被使用的最後一個 slot,就是 last_slot 指向的那個 slot 前面的 slot。
遍歷過程中,每次取一個 slot,如果這個 slot 已被使用,就檢查它對應的鎖等待是否超時。
加鎖事務進入鎖等待狀態之前,會把鎖等待的開始時間記錄到 srv_slot_t 對象的 suspend_time 屬性中,還會把超時時間記錄到 srv_slot_t 對象的 wait_timeout 屬性中。
檢查 slot 對應的鎖等待是否超時,步驟如下:
- 用當前時間減去鎖等待的開始時間(
suspend_time屬性值),得到一個差值。 - 判斷上一步的差值是否大於鎖等待超時時間(
wait_timeout屬性值)。
如果大於,説明這個 slot 對應的鎖等待超時了,需要進一步處理超時邏輯。
3. 處理超時邏輯
超時檢查線程每找到一個鎖等待超時的事務,都有一系列的工作要做,重要工作是從鏈表中刪除對應的鎖結構。
如果事務等待行鎖超時,從鏈表中刪除行鎖結構的步驟如下:
- 從事務對象的
trx_locks 鏈表中刪除行鎖結構。 - 找到 rec_hash 的數組中對應的行鎖結構鏈表,從鏈表中刪除這個行鎖結構。
如果事務等待表鎖超時,從鏈表中刪除表鎖結構的步驟如下:
- 從事務對象的
trx_locks 鏈表中刪除表鎖結構。 - 從表對象的
locks 鏈表中刪除表鎖結構。
從鏈表中刪除對應的鎖結構,是因為鎖等待超時有可能會自動回滾事務,這個行為由系統變量 innodb_rollback_on_timeout 控制。
這個系統變量的默認值為 false,表示鎖等待超時不會回滾事務,修改為 true,鎖等待超時就會回滾事務。
如果事務回滾了,它創建的鎖結構不刪除,這個鎖結構就無主了。為了避免出現無主的鎖結構,刪除操作就必須要做了。
4. 通知超時事務
事務加鎖進入鎖等待狀態,會找到一個空閒的 slot,記錄加鎖的相關信息。
如果這個 slot 對象之前沒有被使用過,InnoDB 會創建一個事件對象,保存到這個 slot 對象的 event 屬性中。
超時檢查線程處理完超時邏輯之後,會觸發這個 slot 對象的 event 屬性中保存的事件,通知對應的事務鎖等待超時了。
收到通知之後,鎖等待事務會進行接下來的處理邏輯,主要幹幾件事:
- 如果事務等待行鎖超時,用當前時間,減去鎖等待的開始時間,得到鎖等待消耗的時間,這個時間會記錄到慢查詢日誌中。
- 生成鎖等待超時報錯信息。
- 如果系統變量 innodb_rollback_on_timeout 的值為 true,回滾事務。
5. 總結
鎖等待超時有兩類參與者,主動者是超時檢查線程,被動者是鎖等待事務。
超時檢查線程的主要流程如下:
- 遍歷 waiting_threads 和 last_slot 之間的 slot。
- 對於已被使用的 slot,判斷鎖等待是否超時。
- 如果超時,處理這個 slot 對應的鎖等待超時邏輯。
- 通知鎖等待超時的事務。
鎖等待事務收到通知之後,也有一些事情要幹,完事之後,這個事務的鎖等待過程也就結束了。