提問式複習:圖文回顧 redo log 相關知識

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

原文連結:提問式複習:圖文回顧 redo log 相關知識

1、如何提升 redo日誌 的寫效能?

  • 為了保證 redo日誌 不丟失,會在磁碟中開闢一塊空間將日誌儲存起來。但是這樣會有一個問題,磁碟的讀寫效能非常的差。
  • 所以 redo日誌 和資料頁一樣,系統都是會分配一塊連續的記憶體,來提升讀寫效能;資料頁對應的是 buffer pool,而 redo日誌 對應的是 log buffer。

buffer pool可以利用「innodb_buffer_pool_size」指定總大小,利用「innodb_buffer_pool_instances」指定例項數,但是必須size大於等於1G才生效。

log buffer 可利用「innodb_log_buffer_size」指定 log buffer 的大小;一片連續的記憶體空間會被劃分為N個512位元組大小的block。

log file 可以利用「innodb_log_file_size」指定每個 log file 的大小,利用「innodb_log_files_in_group」指定一共多少個log file。

2、redo日誌 何時寫入log buffer?

  • 對底層頁面(可能是多個頁面)進行一次原子性訪問,等於一個MTR,即 Mini Transaction。一個 MTR對應一組 redo日誌 。一個事務對應多個語句,一個語句對應多個個MTR,一個MTR對應一組redo日誌,即多個 redo日誌 。
  • 在MTR結束後,會將一組 redo日誌 寫入到log buffer中。

詳情可看下圖:
redo-lsn-offset

3、log buffer 中的 redo日誌 何時刷盤?

  • 當 log buffer 已經被寫入約一半左右,下次再寫入 redo日誌 時,需將 log buffer 的 redo日誌 刷到磁碟檔案中。
  • 當事務結束時,需先將 log buffer 中,被修改的快取頁對應的 redo日誌 刷回磁碟中。
  • 後臺執行緒刷,大概每隔一秒刷一次 log buffer 中的 redo日誌 到磁碟中。
  • 執行checkpoint。
  • 正常關閉伺服器。

4、我們都知道每次寫入 redo日誌 ,都是以組為單位,那麼我們怎麼知道哪些是一組?

  • 在該組中的最後一條 redo日誌 後邊加上一條特殊型別的 redo日誌 ,該型別名稱為「MLOG_MULTI_REC_END」,type欄位對應的十進位制數字為31,該型別的 redo日誌 結構很簡單,只有一個type欄位。

5、如何知道下一次redo日誌改寫到log buffer的哪個位置?

  • buf_free全域性變數,指向log buffer中下個寫入的位置。

6、如何知道下次從log buffer的哪個位置開始刷入磁碟?

  • buf_next_to_write全域性變數,指向log buffer中下個刷回磁碟的位置。

7、如何定位 log buffer 中的 redo日誌 對應哪些被修改的資料頁;在被修改的資料頁中,如何定位到對應的是哪些 redo日誌 ?

  • 修改的快取頁找到對應的 redo日誌
    • lsn
      • 首先,出場一個變數,叫lsn,全稱:log sequence number,日誌序列號。它記錄的是,redo日誌 的總位元組數,初始值為8704。當系統啟動,初始化log buffer 時,lsn 值為 8704+12(一個log block header)=8716
      • 接著,log buffer 是由多個block組成的(可以理解為buffer pull的快取頁),block由三部分組成,log block header(12個位元組)、log block body、log block trailer(4個位元組)。
      • 當第一個 redo日誌 組,如「mt_1」準備被寫入,並且一個block能容納,此時lsn為 8704+12(一個log block header)=8716,假設「mt_1」一共100位元組,那麼「mt_1」寫入後,lsn為8716+100=8816
      • 當第二個 redo日誌 組,如「mt_2」準備被寫入,並且需要跨block才能容納,如跨一個(即包含一個log block header和一個log block trailer),開始寫入前lsn:8816,假設「mt_2」一共1000個位元組,那麼「mt_2」寫入後,lsn為8816+12(一個log header)+4(一個log tail)+1000=9832
    • flush和lsn
      • 當 MTR 結束時,會將被修改過的資料頁對應的資料塊放入 flush連結串列 的表頭中,並且給兩個引數賦值,分別是 old_modification 和 new_modification:old_m 賦值是 MTR 開始前的 lsn 值,而 new_m 賦值是 MTR 結束時的 lsn 值。
      • 如果一個 MTR 修改的資料頁對應的控制塊本來就在 flush連結串列 中,則不調整資料頁對應的資料塊的位置,只是修改 new_modification 的值,old_modification還 是保持第一次進入 flush連結串列 時 lsn 的值。
      • 就是說,在 flush連結串列 中,資料塊是根據第一次修改的時間進行倒序排列的。
    • 通過上面,那麼我們可以根據flush連結串列中,資料塊的 old_modification 和 new_modification 找到對應的一組 redo日誌 ,因為通過 lsn 可以定位到對應 redo日誌 在磁碟檔案中的偏移量(這個下面會講解到)。
  • redo日誌 找到對應的快取頁面
    • redo日誌 的通用結構是:type-spaceId ID-page Number-data,即我們可以根據 redo日誌 的 space ID 和 page Number 即可找到對應的快取頁。
    • 順帶一提:在 InnoDB 中,有一個雜湊表,key為表空間號+頁號,value為快取頁地址。這樣我們可以通過 space ID 和 page Number 快速定位到對應的快取頁。

8、我們知道可以利用 lsn 知道有多少位元組數的 redo日誌 寫入到 log buffer 中,那麼我們能有變數對應的知道有多少位元組數的 redo日誌 被刷入磁碟中嗎?

  • flushed_to_disk_lsn 全域性變數,表示刷到磁碟的日誌量。

9、lsn 和 log file 的偏移量怎麼對得上麼?

  • lsn 初始值是 8704,隨著 redo日誌 的不斷寫入,lsn 不斷增大。而 innodb 中,是利用 block 這個結構來儲存 redo日誌 (不管是 log buffer 還是 log file),而 block 包含三部分,上面已經提到。當 redo日誌 不斷寫入,不斷佔用 block 的空間,那麼 lsn 會增加對應的位元組數,當然了,除了body、也算 header 和 trailer。
  • log file 是由日誌組組成,日誌組最大設定100個檔案數,每個日誌檔案也是由多個512位元組的block映象組成,日誌組第一個日誌檔案前4個block映象用於儲存重要資訊、如checkpoint等、即前2048個位元組不用於儲存 redo日誌 ,即從2048個位元組開始計算 redo日誌 的存放量。
  • log file 的 log file header 中有一個「LOG_HEADER_START_LSN」屬性,標記本 redo日誌 檔案偏移量2048位元組處對應的lsn值。

詳情可看下圖:
redo-lsn-offset

10、log buffer 中的 redo日誌 真的會在事務結束時立馬刷回到磁碟中嗎?

  • 預設是的,這裡有一個引數控制:「innodb_flushing_log_at_trx_commit」,預設值是1
    • 0:事務提交,不會立馬刷到磁碟中,依賴後臺執行緒刷入,即如果此時MySQL或系統掛掉重啟,無法恢復髒頁
    • 1:事務提交,會立馬將log buffer的 redo日誌 刷回磁碟中
    • 2:事務提交,會立馬將log buffer的 redo日誌 刷到作業系統的快取中,而不是刷到磁碟中;如果此時MySQL掛掉了,重啟後不會影響恢復髒頁,而如果是系統掛掉,就無力迴天了。

11、log file 都是迴圈使用,即可以覆蓋,那麼怎麼判斷是否可以覆蓋?

  • log file 中可被覆蓋,那麼首要條件就是 redo日誌 對應的髒頁已經被刷到磁碟中。
  • innodb 有個全域性變數:checkpoint_lsn,它記錄的是可被覆蓋的 redo日誌量。初始值就是lsn的初始值,8704。
    • 什麼是 checkpoint?
      • 當有髒頁被刷到磁碟時,首先在flus連結串列中拿到最舊的快取頁,即需要拿到連結串列尾部的控制塊,然後拿到 old_modification 的值,然後將這個值賦值給 checkpoint_lsn,因為只要是小於 flush 連結串列中最舊的控制塊的 old_modification 的 lsn,就代表可以被覆蓋,畢竟對應的髒頁已經被刷到磁碟中了。
      • 接著,將根據當前的 checkpoint_lsn 獲取對應日誌檔案組的偏移量,記錄為 checkpoint_offset,checkpoint_no 也需要加1,最後將三個資訊記錄在日誌檔案組的 checkpoint1 或 checkpoint2(checkpoint_no為奇數存1,否則存2)。
      • 上面兩步稱為執行一次checkpoint。
  • 我們只需要從日誌檔案組中的 checkpoint1 和 checkpoint2 拿到資訊,然後對比 checkpoint_no 看哪個是最新的,接著拿到checkpoint_lsn,那麼 lsn 小於 checkpoint_lsn 的日誌都可以被覆蓋。

12、系統崩潰重啟,如何利用 redo日誌 進行恢復?

  • redo日誌 進行崩潰恢復主要是利用上面提到的 checkpoint_lsn,因為 checkpoint_lsn 表示可以覆蓋的日誌量,則表示 checkpoint_lsn 之前的 redo日誌 對應的髒頁都已經被刷回到磁碟中。
  • 首先從 redo 日誌組中拿到 checkpoint1 和 checkpoint2,接著判斷誰的 checkpoint_no 大,大的就是最新的一次 checkpoint 執行。
  • 接著拿到對應的 checkpoint_offset,那麼 checkpoint_offset 後的 redo日誌 都需要掃描一遍,然後根據 redo日誌 的內容,對資料頁進行恢復。

13、恢復是掃描一個 redo日誌 ,就進行一次恢復嗎?

  • 問題:
    • 因為根據 redo日誌 恢復資料頁的變更,是直接更新磁碟中的資料頁;掃描一個 redo日誌 ,就進行一次恢復,如果存在多個 redo日誌 記錄同一個資料頁的變更,並且不是連續的,那麼會導致多次隨機IO,效能會非常的差。
  • 解決:
    • 所以會有一個雜湊表,key為 space ID + page Number,value 為資料頁地址。掃描 redo日誌 時,會將同一個 space ID + page Number 的 redo日誌 都放在同一個槽下。
    • 接著遍歷雜湊表,執行每一個 space ID + page Number 對應所有的 redo日誌 。
  • 好處:
    • 避免了多次的隨機IO,提升恢復的速度。
    • 按順序根據 redo日誌 進行恢復,避免出現恢復的順序問題。

詳情可看下圖:
redo-恢復

14、恢復時,如何知道什麼時候結束?

  • 首先,我們知道,在日誌組裡,有多個block映象,然後 redo日誌 刷盤,是按順序填入每個block的,只有前一個block填滿了,才接著填下一個
  • 接著,每個 block 的大小都是 512 個位元組,包括 log block header、log block body 和 log block trailer。在block的頁面結構中,log block header 頭部有一個「LOG_BLOCK_HDR_DATA_LEN」的屬性,該屬性值記錄了當前block裡使用了多少位元組的空間。對於被填滿的block來說,該值永遠為512。
  • 最後,所以只管往後面一直掃,直到 log block header 中 「LOG_BLOCK_HDR_DATA_LEN」屬性不是512的 block,那麼就是恢復的終點了。

15、如何相容髒頁已經已經刷回磁碟,但是 redo日誌 沒有刷回磁碟的場景?

  • 場景復現:
    • 當我們提交事務時,會根據引數「innodb_flush_at_trx_commit」來做下一步操作,如果是0或者2,那麼此時的日誌並沒有刷回到磁碟中,而是留在log buffer中或作業系統快取中。
    • 接著,如果有後臺執行緒將 LRU 連結串列或 flush 連結串列的某些髒頁刷回磁碟中,刷回後;但是此時對應的 redo日誌 還停留在上面提到的兩個地方,如果伺服器當機,那麼對應的 redo日誌 就會丟失了。
    • 因為刷 LRU 連結串列、flush 連結串列和刷 redo日誌 的後臺執行緒,往往都是不同的執行緒,無法知道對應的 redo日誌 是否已經刷回去。
  • 相容:
    • 每個資料頁都有一個稱之為 File Header 的部分,在 File Header 裡有一個稱之為 FIL_PAGE_LSN 的屬性,該屬性記載了最近一次修改頁面時對應的 lsn 值(其實就是頁面控制塊中的 newest_modification 值)。
    • 如果在做了某次 checkpoint 之後有髒頁被重新整理到磁碟中,那麼該頁對應的 FIL_PAGE_LSN 代表的 lsn 值肯定大於 checkpoint_lsn 的值,凡是符合這種情況的頁面就不需要重複執行 lsn 值小於 FIL_PAGE_LSN 的 redo日誌 了,

最後,祝大家國慶節快樂!

相關文章