MySQL學習總結:提問式回顧 undo log 相關知識

不送花的程式猿發表於2021-10-04

原文連結: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。

大概如下圖:
undo

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 日誌,因為此階段在事務提交後執行。
  • 擴充套件點:

    • 每當新插入一條記錄,都會先判斷垃圾連結串列的頭節點代表的已刪除記錄的儲存空間是否滿足新紀錄,如果滿足,直接複用。
      • 疑問:如果新插入的記錄一直都無法使用垃圾連結串列的頭節點對應的儲存空間,豈不是一直會存在碎片空間?下面第三點會解釋!
    • 如果無法滿足,則需要直接向頁面申請新的空間來儲存。
    • 但如果此時頁面沒有足夠的空間來儲存新紀錄,那麼就會判斷「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 日誌

相關文章