原文鏈接:MySQL學習總結:提問式回顧 undo log 相關知識
1、redo 日誌支持恢復重做,那麼如果是回滾事務中的操作呢,也會有什麼日誌支持麼?
- 也回滾已有操作,那麼就是想撤銷,對應的有撤銷日誌,也叫做 undo log。
- undo 日誌分爲兩大類:「TRX_UNDO_INSERT」和「TRX_UNDO_UPDATE」,undo 日誌需根據大類分開存儲,不能混淆。
「TRX_UNDO_INSERT」對應的是insert語句、「TRX_UNDO_UPDATE」對應的是update語句和delete語句
- 問題:那如何定位事務中對哪些記錄做了改動?
- 數據頁記錄中,有一個隱藏列「trx_id」,用於記錄當前操作此記錄的事務。
- MySQL會在內存維護一個全局變量,專門爲事務分配事務ID。
- 每當需要爲事務分配ID,則拿到上述全局變量,然後自增1。
每當上述全局變量自增到256的倍數時,需要將此值刷新到磁盤中(系統空間表頁號爲5的頁面的 Max Trx ID 屬性中)。
- 記錄修改了,如何定位到對應的 undo log?
- 在記錄中有一個隱藏列「roll_point」,它會指向對應的 undo 日誌。
2、undo 日誌都是存在哪些地方,事務對錶進行編輯,怎麼分配的?
- InnoDB支持128個回滾段,一個回滾段對應一個「Rollback Segment Header」頁面。
- 回滾段分爲兩大類:第0號、第33~127號屬於一類,0號存放在系統表空間、其他的可以放在系統表空間或者自己配置 undo 表空間,這類用於存放普通表改動對應的 undo 日誌;第1~32號屬於一類,存放在臨時表空間中,這類用於存放臨時表改動對應 undo 日誌。
- InnoDB在系統表空間的5號頁面的某個區域中,包含了128個8個字節大小的格子,用於保存128個「Rollback Segment Header」頁面的地址。
-「Rollback Segment Header」頁面有一個重要部分是「TRX_RSEG_UNDO_SLOTS」:它表示各個 Undo 頁面鏈表的 first undo page 的頁號集合,也叫 undo slots 集合。- 因爲一個頁號佔用4字節,而「TRX_RSEG_UNDO_SLOTS」一共是4096個字節,所以一共可以存儲1024個lot。
- 每個 slot 的初始值是 FIL_NULL,表示沒有分配給其他事務使用。
- 當事務需要分配 undo 頁面鏈表時,
- 先到回滾段對應的兩個 cached 鏈表找是否有可用的 undo 頁面;
insert undo cache 鏈表和 update undo cache 鏈表。
- 如果有則直接重用,否則需要回到「Rollback Segment Header」頁中繼續尋找;
- 在「Rollback Segment Header」頁中便利1024個 slot,看 slot 的值是否爲 FIL_NULL,如果是的話,則申請一個 undo 頁面作爲該 undo 頁面鏈表的 first undo page,接着將此頁面的頁號賦值給當前 slot。
- 否則,表明已經有事務佔用此 slot,需要繼續往下尋找下一個 slot。
- 如果到了最後一個回滾段的最後一個 slot,都沒有找到可用的 slot,則給客戶端返回異常:Too many active concurrent transactions。
大概如下圖:
3、通過上面,我們都知道「roll_point」可以定位到對應的 undo 日誌,但 undo 日誌也是保存在磁盤中的,那又是怎麼定位的呢?
- 我們都知道聚簇索引以及二級索引,都是類型爲「FIL_PAGE_INDEX」的頁面;而 undo 日誌也是存儲在磁盤的,它對應的頁面類型是「FIL_PAGE_UNDO_LOG」。
- roll_point 組成部分:
- is_insert:是否是「TRX_UNDO_INSERT」大類的 undo 日誌
- rseg id:回滾段編號
- page number:指針,指向 undo 日誌所在頁面的頁號。
- offset:指針,指向 undo 日誌在頁面中的偏移量。
- 所以,我們可以根據「roll_point」中的屬性,找到對應的回滾段、接着找到對應的頁面,最好定位到頁面中的偏移量。
4、如果一個頁面無法存儲當前事務生成的日誌,要多個頁面才能完成,undo 日誌間如何聯繫?
- undo 日誌頁面有一個特有的部分:「Undo Page Header」。
數據頁都有一個共有的部分:「File Header」,頁面間可利用鏈表完成聯繫,就是靠「File Header」 中的「FIL_PAGE_PREV」和「FIL_PAGE_NEXT」屬性。
但這是頁面之間的鏈表,如果要做到 undo 日誌的鏈表,還需更細的連接信息。
- TRX_UNDO_PAGE_TYPE:上面提到的 und 日誌分爲兩個大類:「TRX_UNDO_INSERT」和「TRX_UNDO_UPDATE」
- TRX_UNDO_PAGE_START:當前頁面存儲第一條 undo 日誌的開始偏移量
- TRX_UNDO_PAGE_FREE:當前頁面存儲最後一條 undo 日誌的結束偏移量
- TRX_UNDO_PAGE_NODE:
- Prev Node Page Number:前一個節點的頁號
- Prev Node Offset:前一個節點頁內的偏移量
- Next Node Page Number:後一個節點的頁號
- Next Node Offset:後一個節點頁內的偏移量
- 所以,可以利用每個 undo 日誌頁「Undo Page Header」的「TRX_UNDO_PAGE_NODE」屬性來組成一個鏈表。
5、undo 日誌也支持重用麼?如果支持,如何覆蓋 undo 日誌?
- 重用條件:
- 首要條件:undo 頁面鏈表對應的事務已經提交
- 第二:undo 頁面鏈表只包含一個 undo 頁面
- 第三:該頁面已使用的空間小於整個頁面空間的3/4
- 重用策略:
- insert undo 鏈表:因爲對於新增記錄,只要事務提交了,對應的 undo 日誌就沒啥用了,所以可以直接覆蓋。
- update undo 鏈表:對於更新/刪除記錄,即使提交了,也不能立馬刪除對應的 undo 日誌,因爲 MVCC 需要利用此鏈表做文章;所以只能在後面接着寫入 undo 日誌,即一個 undo 頁面,寫入多組 undo 日誌。
6、如果 undo 日誌支持重用,那怎麼知道從哪裏開始寫入第二組 undo 日誌?
- undo 日誌的頁面有一個非常重要的部分:Undo Log Header。
- 它包含:
- TRX_UNDO_TRX_ID:本組 undo 日誌對應的事務id
- TRX_UNDO_TRX_NO:事務提順序,事務提交後會生成一個序號;先提交的序號小。
- TRX_UNDO_LOG_START:本組 undo 日誌 中第一條 undo 日誌在頁面中的偏移量
- TRX_UNDO_NEXT_LOG:下一組 undo 日誌在頁面中開始的偏移量(支持 undo 日誌頁面複用)
- TRX_UNDO_PREV_LOG:上一組 undo 日誌在頁面中開始的偏移量(支持 undo 日誌頁面複用)
- ......
- 因此,只需拿到「TRX_UNDO_NEXT_LOG」對應的偏移量,即可知道在哪裏開始繼續寫入第二組 undo 日誌。
7、insert語句和 undo 日誌
- 插入一條類型爲「TRX_UNDO_INSERT_REC」 的 undo 日誌,用於支持回滾操作。
- undo 日誌裏最主要是記錄了插入的記錄的主鍵信息:<len,value>列表
- len 爲主鍵類型所佔存儲空間的長度,例如主鍵類型爲int,佔用4字節,那麼len爲4
- value 爲主鍵的真實值,例如 id 列爲主鍵,插入記錄的主鍵 id=2,那麼value爲2
8、delete語句和 undo 日誌
-
刪除階段:
- 插入一條類型爲「TRX_UNDO_DEL_MARK_REC」的 undo 日誌,用於支持回滾操作。
- 將該記錄的 trx_id 和 roll_point 舊值記錄到 undo 日誌對應的屬性中。
- delete_mark 階段:將記錄的「delete_flag」置爲1,表示已經刪除
- 還是保留在正常記錄鏈表中,保留這個中間狀態是爲了支持MVCC
- 還會修改 trx_id、roll_point等隱藏列
- purge階段:當事務提交時,會有專門的線程真正刪除此記錄
- 這裏的真正刪除指的是,將記錄從正常記錄鏈表中移除,加入到垃圾記錄鏈表的表頭。
- 將記錄的 next_record 指向 Page Header 中的 PAGE_FREE 屬性
- 將 Page Header 的 PAGE_FREE 屬性執行此記錄
- 修改 Page Header 中的 PAGE_GARBAGE,表示頁面中可重用的字節數量
- purge 階段不需要 undo 日誌,因爲此階段在事務提交後執行。
- 這裏的真正刪除指的是,將記錄從正常記錄鏈表中移除,加入到垃圾記錄鏈表的表頭。
- 插入一條類型爲「TRX_UNDO_DEL_MARK_REC」的 undo 日誌,用於支持回滾操作。
-
擴展點:
- 每當新插入一條記錄,都會先判斷垃圾鏈表的頭節點代表的已刪除記錄的存儲空間是否滿足新紀錄,如果滿足,直接複用。
- 疑問:如果新插入的記錄一直都無法使用垃圾鏈表的頭節點對應的存儲空間,豈不是一直會存在碎片空間?下面第三點會解釋!
- 如果無法滿足,則需要直接向頁面申請新的空間來存儲。
- 但如果此時頁面沒有足夠的空間來存儲新紀錄,那麼就會判斷「PAGE_GARBAGE」中的碎片空間和剩餘的可用空間加起來是否能滿足,如果可以,那麼就會進行頁面的重新組織
- 開闢一個臨時頁面,把本頁面的記錄依次插入
- 接着將臨時頁面複製到本頁面,接着釋放碎片空間。
- 疑問:如果「PAGE_GARBAGE」中的碎片空間和剩餘的可用空間加起來都不能滿足呢?是不是會有頁面分裂?
- 否則,如果頁面的碎片空間和剩餘空間都不足以存放新紀錄,那麼只能進行頁面分裂。
- 每當新插入一條記錄,都會先判斷垃圾鏈表的頭節點代表的已刪除記錄的存儲空間是否滿足新紀錄,如果滿足,直接複用。
9、update語句和 undo 日誌
- update 分兩種情況:更新主鍵和不更新主鍵
- 不更新主鍵:也分爲兩種情況,一種是更新前後所佔存儲空間大小不變;另外一種是,更新前後所佔存儲空間變大或變小。
- 就地更新:
- 在原有記錄更新
- 插入一條類型爲「TRX_UNDO_UPD_EXIST_REC」的 undo 日誌,用於支持回滾操作。
- 包含主鍵各列信息(<len,value>列表)、索引列各列信息(<pos,len,value>列表)、被更新的列更新前信息。
- 先刪除再插入:
- 用戶線程同步刪除舊記錄,更新相關的頁面統計信息。
- 在當前頁申請新的空間插入一條變更後的記錄,如果頁面剩餘的存儲空間不足,則需要進行頁面分裂。
- 插入一條類型爲「TRX_UNDO_UPD_EXIST_REC」的 undo 日誌
- 就地更新:
- 更新主鍵:
- 對舊id記錄執行刪除操作,注意:這裏只是執行 delete_mark 階段,避免其他事務無法訪問此記錄。
插入一條類型爲「TRX_UNDO_DEL_MARK_REC」的 undo 日誌
- 接着根據更新後的個列值創建一條新紀錄,並插入到聚簇索引中。
插入一條類型爲「TRX_UNDO_INSERT_REC」 的 undo 日誌