redo日志


redo日志

作用

innoDB存儲引擎中,需要在服務器故障重啟后,能夠准確的恢復所有已提交的數據,保證數據持久性;如某個事務在內存Buffer Pool中已被提交(臟頁),但服務器突然故障,數據就丟失了;

為了解決這個問題,可以采用修改頁面刷新到磁盤,但因為可能只修改了一條記錄,沒必要實時刷新浪費時間,而且修改的記錄並不一定是連續的,隨機IO刷新較慢。

可以將已提交事務修改的記錄記錄下來,即某個表空間中某頁的某個偏移量的值更新為多少,這個記錄的文件就稱為redo log。相比刷新內存中的頁面到磁盤,redo log刷新到磁盤的內容小了很多,而且是一個順序寫入磁盤的過程。

redo日志不止記錄索引插入/更新記錄等操作,還有執行這個操作影響到的其他動作,如頁分裂新增目錄項記錄,修改頁信息等對數據頁做的任何修改等等。

和binlog區別:binlog記錄的是頁已經正式落盤的操作且是包含所有存儲引擎,redo日志記錄InnoDB引擎下仍然在buffer pool中的操作,用於系統奔潰時恢復臟頁。

日志

日志格式

  • type:類型

    • MLOG_1BYTE:1 :表示在頁面的某個偏移量處寫入1個字節的redo日志類型。
    • MLOG_2BYTE:2
    • MLOG_4BYTE:4
    • MLOG_8BYTE:8
    • MLOG_WRITE_STRING:30
    • MLOG_REC_INSERT:9:表示插入一條使用非緊湊行格式記錄時的redo日志類型(如redundant)
    • MLOG_COMP_REC_INSERT:38:表示插入一條使用非緊湊行格式記錄時的redo日志類型(如compact/dynamic/compressed)
    • MLOG_COMP_REC_DELETE:42:表示刪除一條使用緊湊行格式記錄的redo日志類型
    • MLOG_COMP_LIST_START_DELETE和MLOG_COMP_LIST_END_DELETE:批量刪除,可以很大程度上減少redo日志的條數
  • space id:表空間

  • page number:頁號

  • data:真實數據(以MLOG_COMP_REC_INSERT為例)

    • n_fileds:當前記錄的字段數
    • n_uniques:決定該記錄的唯一字段列數量,如有主鍵的是主鍵數,唯一二級索引是索引列數+主鍵列數,普通二級索引一樣;插入時可根據這個字段進行排序
    • field1_len-fieldn_len:若干字段占用存儲空間的大小
  • offset:記錄前一條記錄在頁面中的位置,方便修改頁面中的記錄鏈表,前一條記錄維護的next_record屬性

    • end_seg_len:通過這個字段可得知當前記錄占用存儲空間大小
  • len:類型為MLOG_WRITE_STRING時才有的,表示具體數據占用的字節數

redo日志內存內操作

Mini-Transaction

Mini-Transaction(mtr)是對底層頁面中的一次原子訪問的過程(如MAX_ROW_ID生成/索引記錄插入)。一個事務可以包含多個mtr,一個mtr包含多條redo日志。

以組的形式寫入日志

針對要保證原子性的操作必須以的形式來記錄redo日志,以插入一條記錄為例,當出現頁分裂時,會涉及到申請數據頁,改動系統頁,改各種段/區的統計信息,free等鏈表的信息,新增目錄項記錄等等操作,要么全都執行完成要么全都沒執行。

划分日志組:通過為這一系列動作的日志的最后一條日志后添加一個特別的日志,類型為MLOG_MULTI_REC_END:31作為結尾。

如果某個原子操作只有一條日志記錄,那么給這條日志類型的第一個比特位設為1,不然就是產生了一系列日志。

redo日志寫入過程

MySQL使用512字節的頁來記錄redo日志,這種頁被稱為blockblocklog block headerlog block trailerlog block body組成,header和trailer存儲頁的管理信息。

服務器啟動時會先向系統申請一片連續的內存作為redo日志緩沖區log buffer,以兩個事務為例,事務T1和T2是交替執行的,所以可能是交替存儲到log buffer的。它們各自包含兩個mtr,每個mtr在運行過程中,會先將redo log存在一個地方,等到這個mtr執行結束,就會將這個mtr產生的所有redo日志全部復制到log buffer,一定時間后才會沖刷到磁盤。

log buffer中寫入日志是順序的,所以block不會出現碎片空間,InnoDB提供一個全局變量buf_free來指明后續寫入的redo日志應該往block的哪個位置中寫入,即從這個位置開始后面都是空閑的。

redo日志刷盤

刷盤准備

當修改buffer pool中的頁時,會將這個臟頁的控制塊插入到flush鏈表中,控制塊存儲了兩個變量:oldest_modification被加載到buffer pool中第一次修改mtr開始時對應的lsn值,newest_modification每次mtr修改結束時對應的lsn值;控制塊按照oldest_modification從大到小排序存儲。

一個mtr可能修改多個頁,所以多個控制塊的oldest_modification/newest_modification可能一樣。

刷盤時機
  • log buffer空間不足,空閑空間小於一半時
  • 事務提交時,buffer pool中的臟頁可以先不刷盤,但其中的log buffer需要刷盤,防止丟失
  • 后台線程定時刷盤
  • 正常關閉服務器時
  • checkpoint時:批量從flush鏈表中刷出臟頁:如果系統修改頁面頻繁,且不能將臟頁刷出,則不能及時checkpoint,可能會直接使用用戶線程同步的從flush鏈表中最早修改的臟頁刷盤,這樣這些臟頁對應的redo日志就沒用了,就可以checkpoint了
redo日志文件存儲

在MySQL的數據目錄下,(由innodb_log_group_home_dir確定存儲位置,由名稱可知存儲形式是一個日志文件組)名為ib_logfile0...n的文件,文件個數決定文件名稱后綴,由系統參數innodb_log_files_in_group確定文件個數,每個文件的大小由innodb_log_file_size指定。

每個ib_logfile順序循環寫入log buffer中的block,會出現文件被覆蓋的現象。

日志文件格式
  • 前2048個字節(4個block):存儲管理信息

    • log file header:存儲當前文件的redo日志版本,文件開始的LSN值等等

    • checkpoint1:標記日志文件可覆蓋信息

      • LOG_CHECKPOINT_NO:checkpoint次數,遞增記錄
      • LOG_CHECKPOINT_LSN:redo日志文件可被覆蓋的最大lsn值
      • LOG_CHECKPOINT_OFFSET:lsn對應日志文件的偏移量
      • LOG_CHECKPOINT_LOG_BUF_SIZE
    • 沒用

    • checkpoint2

  • 后面的字節:存儲block內容,會被循環使用

幾個全局變量
  • Log Sequeue Number(lsn):InnoDB記錄已寫入的redo log量的全局變量,包括log buffer寫入的日志,初始值為8704,此時日志文件的偏移量為2048字節。

    • 第一次一個mtr生成一組日志並加入到log buffer時,lsn=8704+日志寫入量+log block header
    • 再次生產一組日志時,在同一個block 內,且能容納,就只需要加上日志寫入量;
    • 又生成一組日志,但占用量較大,當前block中剩余空間不可容納,需要占用到下下個block時,lsn=lsn+日志寫入量+2 *log block header+2 *log block trailer;
  • flushed_to_disk_lsn:刷新到磁盤中的redo日志量的全局變量

  • buf_next_to_write:標記已有哪些log buffer中的日志被刷盤的全局變量

  • innodb_flush_log_at_trx_commit=?:表示用戶線程提交時需要將該事務執行過程中產生的所有redo日志刷盤到磁盤(1)還是交給后台線程操作(0),或者先寫入到緩沖區中(2)。

checkpoint

判斷某些redo日志占用的磁盤空間是否可以被覆蓋,即它對應的臟頁是否已經刷新到磁盤

checkpoint_lsn:代表當前系統中可以被覆蓋(臟頁已經被刷盤)的redo日志總量,初始值為8074。

checkpoint步驟:

  • 計算可以被覆蓋的redo日志對應的lsn最大值(flush鏈表最小oldest_modification值對應的lsn之前的日志都是可以被覆蓋掉的,因為flush鏈表不存儲已經被刷盤的臟頁),賦值給checkpoint_lsn
  • checkpoint_lsn對應的redo日志文件組偏移量checkpoint_offset及此次checkpoint的編號(總共做了多少次checkpoint,遞增)checkpoint_no寫到日志文件頭部的checkpoint1/2
奔潰恢復

恢復奔潰發生時,flush鏈表中還未寫入磁盤的臟頁更改。

  • 確定恢復的起點:比較日志中文件中checkpoint1checkpoint2中最大的checkpoint_no,然后從對應的checkpoint_lsn(之前的都是可覆蓋的,說明已經被刷盤了)開始恢復

  • 確定恢復的終點:比較每個日志文件log block headerLOG_BLOCK_HDR_DATA_LEN屬性,如果不為512則說明是最后一個填充的日志文件

  • 恢復方法

    • 哈希表:計算redo日志的hash(表空間id+頁號),相同值的放在一個slot中,並根據生成時間排序鏈表形式連接,這樣可以一次修復一個頁面,避免多余的隨機IO
    • 跳過奔潰時已被恢復的頁:flush鏈表中可能存在已經被刷盤的臟頁,所以會根據臟頁的file header中FIL_PAGE_LSN屬性即控制塊中的newest_modification是否大於checkpoint_lsn,如果是那么就不需要執行小於newest_modificationFIL_PAGE_LSN的redo日志了


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM