MySQL redo

caohongfeng666發表於2021-01-03

標籤:MySQL

標籤:MySQL結構

標籤:MySQL redo


Redo log 相關結構和記入過程:


1. log_sys物件


(1). log_sys作用

log_sys是redo日誌系統的關鍵全域性變數。負責三項事情:

log record從mtr_buf複製到logbuffer:

使用者執行緒mtr_commit時,將mtr_buf中的redo record,拷貝到log buffer中;


log record從log buffer複製到logfile:write_mutex控制logbuffer順序的刷盤;


dirty page從buffer pool刷寫到datafile:log_flush_order_mutex控制flushlist的順序刷盤;執行CHECKPOINT或preflush。


其中,三個鎖分別控制

log_t互斥訪問(mutex)、

日誌刷盤(write_mutex)、

資料刷盤(log_flush_order_mutex)的操作。



(2). 與日誌組、日誌相關變數


ulint n_files:Ib_logfile的檔案個數。

lsn_t file_size:檔案大小。

ulint space_id:Redo log 的space id, 固定大小,值為SRV_LOG_SPACE_FIRST_ID。

ulint state:LOG_GROUP_OK 或者 LOG_GROUP_CORRUPTED。

lsn_t lsn:該group內寫到的lsn。

lsn_t lsn_offset:上述lsn對應的檔案偏移量。在log_group_t裡的lsn和lsn_offset欄位已經記錄了某個日誌lsn和其存放在檔案內的偏移量之間的對應關係。

                   計算時需要結合LOG_FILE_START_LSN,判斷是否跨了logfile檔案。

byte** file_header_bufs:

      Buffer區域,用於設定日誌檔案頭資訊,並寫入ib logfile。當切換到新的ib_logfile時,更新該檔案的起始lsn,寫入頭部。 

      頭部資訊還包含: LOG_GROUP_ID, LOG_FILE_START_LSN(當前檔案起始lsn)、LOG_FILE_WAS_CREATED_BY_HOT_BACKUP(函式log_group_file_header_flush)。

lsn_t scanned_lsn:用於崩潰恢復時輔助記錄掃描到的lsn號。

byte* checkpoint_buf:

      Checkpoint緩衝區,用於向日志檔案寫入checkpoint資訊(下文詳細描述)。

      只有做完checkpoint後,其之前的日誌才可以不再保留,否則系統崩潰時則無法恢復。在系統崩潰後的恢復,需要從checkpoint點開始。

      但我們需要把checkpoint的相關資訊持久化的儲存下來,才能在系統崩潰時不會丟失這些檢查點相關的資訊。Checkpoint相關的資訊只存放在ib _logfile0中。     

lsn_t write_lsn:最近一次完成寫入到檔案的LSN。

lsn_t current_flush_lsn:當前正在fsync到的LSN。

lsn_t flushed_to_disk_lsn:最近一次完成fsync到檔案的LSN。

      

innodb_log_files_in_group=4

innodb_log_file_size=4G

總檔案大小: 17179869184

log_sys->max_modified_age_async = 12175607164 (71%)

log_sys->max_modified_age_sync = 13045293390 (76%)

log_sys->max_checkpoint_age_async = 13480136503 (78%)

log_sys->max_checkpoint_age = 13914979615 (81%)


噹噹前未刷髒的最老lsn和當前lsn的距離超過max_modified_age_async(71%)時,且開啟了選項innodb_adaptive_flushing時,page cleaner執行緒會去嘗試做更多的dirty page flush工作,避免髒頁堆積。

噹噹前未刷髒的最老lsn和當前Lsn的距離超過max_modified_age_sync(76%)時,使用者執行緒需要去做同步刷髒,這是一個效能下降的臨界點,會極大的影響整體吞吐量和響應時間。

當上次checkpoint的lsn和當前lsn超過max_checkpoint_age(81%),使用者執行緒需要同步地做一次checkpoint,需要等待checkpoint寫入完成。

當上次checkpoint的lsn和當前lsn的距離超過max_checkpoint_age_async(78%)但小於max_checkpoint_age(81%)時,使用者執行緒做一次非同步checkpoint(後臺非同步執行緒執行CHECKPOINT資訊寫入操作),無需等待checkpoint完成。

      

(3). 與redo log 記憶體緩衝區相關的成員變數:


變數名描述

ulint buf_size:Log buffer 大小,受引數innodb_log_buffer_size控制,但可能會自動extend。

byte* buf:Log buffer起始位置指標。

ulint buf_free:Log buffer中當前空閒可寫的位置。redo record可拷貝寫入的buffer的位置(buf_free)。

ulint max_buf_free:值為log_sys->buf_size / LOG_BUF_FLUSH_RATIO - LOG_BUF_FLUSH_MARGIN.

                     其中: LOG_BUF_FLUSH_RATIO=2, LOG_BUF_FLUSH_MARGIN=(4 * 512 + 4* page_size),page_size預設為16k.

                     當buf_free超過該值時,可能觸發使用者執行緒去寫redo;在事務拷redo record到buffer後,也會判斷該值,如果超過buf_free,設定log_sys->check_flush_or_checkpoint為true。

ulint buf_next_to_write:Log buffer偏移量,下次寫入redo log檔案的起始位置,即本次寫入的結束位置。redo緩衝區將要向磁碟刷盤的位置(buf_next_to_write)

volatile bool is_extending:Log buffer是否正在進行擴充套件 (防止過大的redo log entry無法寫入buffer), 實際上,當寫入的redo log長度超過buf_size/2時,就會去呼叫函式log_buffer_extend,一旦擴充套件Buffer,就不會在縮減回去了!

ulint write_end_offset:本次寫入的結束位置偏移量(從邏輯來看有點多餘,直接用log_sys->buf_free就行了)。      


(4). 和Checkpoint檢查點相關的成員變數:


變數名描述

ib_uint64_t next_checkpoint_no:每完成一次checkpoint遞增該值。

lsn_t last_checkpoint_lsn:最近一次checkpoint時的lsn,每完成一次checkpoint,將next_checkpoint_lsn的值賦給last_checkpoint_lsn。

lsn_t next_checkpoint_lsn:下次checkpoint的lsn(本次發起的checkpoint的lsn)。

mtr_buf_t* append_on_checkpoint:

            5.7新增,在做DDL時(例如增刪列),會先將包含MLOG_FILE_RENAME2日誌記錄的buf掛到這個變數上。 

            在DDL完成後,再清理掉。(log_append_on_checkpoint),主要是防止DDL期間crash產生的資料詞典不一致。 

ulint n_pending_checkpoint_writes:大於0時,表示有一個checkpoint寫入操作正在進行。使用者發起checkpoint時,遞增該值。後臺執行緒完成checkpoint寫入後,遞減該值(log_io_complete)

rw_lock_t checkpoint_lock:checkpoint鎖,每次寫checkpoint資訊時需要加x鎖。由非同步io執行緒釋放該x鎖

byte* checkpoint_buf:Checkpoint資訊緩衝區,每次checkpoint前,先寫該buf,再將buf刷到磁碟。


2. 日誌檔案頭

每個日誌檔案的前2048位元組是存放的檔案頭資訊。


其中幾個重要的欄位在這裡加以說明:

日誌檔案頭共佔用4個OS_FILE_LOG_BLOCK_SIZE的大小,即2KB。這裡對部分欄位做簡要介紹:

1) LOG_GROUP_ID               這個log檔案所屬的日誌組,佔用4個位元組,當前都是0;

2) LOG_FILE_START_LSN     這個log檔案記錄的開始日誌的lsn,佔用8個位元組;

3) LOG_FILE_WAS_CRATED_BY_HOT_BACKUP   備份程式所佔用的位元組數,共佔用32位元組;

4) LOG_CHECKPOINT_1/LOG_CHECKPOINT_2   兩個記錄InnoDB checkpoint資訊的欄位,分別從檔案頭的第二個和第四個block開始記錄,只使用日誌檔案組的第一個日誌檔案。

從地址2KB偏移量開始,其後就是順序寫入的各個日誌塊(log block)。



3. 日誌塊

日誌塊結構

所有的redo日誌記錄是以日誌塊為單位組織在一起的,日誌塊的大小為OS_FILE_LOG_BLOCK_SIZE(預設值為512位元組),所有的日誌記錄以日誌塊為單位順序寫入日誌檔案。

每一條記錄都有自己的LSN(log sequence number, 表示從日誌記錄建立開始到特定的日誌記錄已經寫入的位元組數)。

每個日誌塊包含一個日誌頭段(12位元組)、一個尾段(4位元組),以及一組日誌記錄(512 – 12 – 4 = 496位元組) 。



二、 日誌寫入簡要過程

1. mtr log生成


2. 從mtr_buf到logbuffer

拷貝mtr中的record

unsigned long buf_free:buf中空閒的第一個位置,按照該位置向logbuffer中拷貝record。

max_buf_free:推薦的buf_free的最大值,會判斷該值,如果超過max_buf_free,需要刷logbuffer;並設定log_sys->check_flush_or_checkpoint為true。


3. 從log buffer到logfile

日誌的刷盤是通過呼叫void log_write_up_to( lsn_t lsn, bool flush_to_disk),如果flush_to_disk為True,則表示將引數lsn之前日誌都write&flush;同時更新相應偏移量。


三、 mtr 

1. Mini transaction


Mini transaction(簡稱mtr)是InnoDB對物理資料檔案操作的最小事務單元,用於管理對Page加鎖、修改、釋放、以及日誌提交到公共buffer等工作。

一個mtr操作必須是原子的,一個事務可以包含多個mtr。每個mtr完成後需要將本地產生的日誌拷貝到公共緩衝區,將修改的髒頁放到flush list上。


2. mtr_t結構


struct mtr_t {

    /* mtr_t 內嵌一個結構體Impl. */

    struct Impl {

      mtr_buf_t m_memo;         /* 一個被加鎖的物件以及它加的鎖. */

      mtr_buf_t m_log;          /* mini-transaction 的 log. */

      bool m_made_dirty;        /* 是否修改了Buffer Pool中的Page為髒頁. */

      bool m_modifications;     /* 是否修改了Buffer Pool中的Page. */

      ib_uint32_t m_n_log_recs; /* 該 mini-transaction 包含多少條log. */

      mtr_log_t m_log_mode;     /* mini-transaction 的工作模式(MTR_LOG_ALL, MTR_LOG_NO_REDO,MTR_LOG_NONE). 

                                 Mtr的工作模式,包括四種: 

                                 MTR_LOG_ALL:預設模式,記錄所有會修改磁碟資料的操作;

                                 MTR_LOG_NONE:不記錄redo,髒頁也不放到flush list上;

                                 MTR_LOG_NO_REDO:不記錄redo,但髒頁放到flush list上;

                                 MTR_LOG_SHORT_INSERTS:插入記錄操作REDO,在將記錄從一個page拷貝到另外一個新建的page時用到,此時忽略寫索引資訊到redo log中。*/

      mtr_state_t m_state;      /* mini-transaction 的狀態: MTR_STATE_INIT, MTR_STATE_ACTIVE, MTR_STATE_COMMITTING, MTR_STATE_COMMITTED. */

    }

}


3. mtr_t中成員memo

是個latch持有狀態的陣列列表,採用的是dyn_array_t的動態記憶體結構來儲存的,每個單元儲存的是 mtr_memo_slot_t 。

m_memo中元素是mtr_memo_slot_t, 記錄加鎖的物件和加鎖的型別.

object是latch的物件,可以是rw_lock_t物件,也可以是buf_block_t物件。


/** mini-transaction memo stack slot. */

struct mtr_memo_slot_t {

  void *object;  /* 加鎖的物件. */

  ulint type;    /* 持有的鎖型別,W or R. */

};


4. mt_t中的成員log

是也是一個dyn_array_t動態結構的記憶體,用來儲存mtr產生的日誌資訊。日誌的寫入是通過mtr0log.h來寫入的。


重做日誌雖然有很多種型別,但重做的日誌格式是統一的,日誌格式是有日誌頭和日誌體組成。

日誌頭資訊是由type、space和page no組成,由mlog_write_initial_log_record_fast函式寫入到mtr_t的log中的。

log body的資料寫入是通過mtr0log.h中的日誌寫入方法進行寫入的,每寫入一條操作日誌,n_log_recs會加1。


5. mini-transaction 具體流程

mtr_t mtr

mtr.start()


/* ... */

/* 寫入資料至 mini-transaction 的 m_log. */

/* ... */


mtr.commit()


(1). mini-transaction 的start()


/** 啟動一個 mini-transaction. */

@param synctrue if it is a synchronous mini-transaction

@param read_onlytrue if read only mini-transaction */

void mtr_t::start(bool sync, bool read_only) {

  UNIV_MEM_INVALID(this, sizeof(*this));


  UNIV_MEM_INVALID(&m_impl, sizeof(m_impl));


  m_sync = sync;


  m_commit_lsn = 0;


  new (&m_impl.m_log) mtr_buf_t();  /* 記錄Redo log的mtr本地Buffer. */

  new (&m_impl.m_memo) mtr_buf_t();


  /* 初始化 mini-transaction 欄位. */

  m_impl.m_mtr = this;

  m_impl.m_log_mode = MTR_LOG_ALL;

  m_impl.m_inside_ibuf = false;

  m_impl.m_modifications = false;

  m_impl.m_made_dirty = false;

  m_impl.m_n_log_recs = 0;

  m_impl.m_state = MTR_STATE_ACTIVE;

  m_impl.m_flush_observer = NULL;


  ut_d(m_impl.m_magic_n = MTR_MAGIC_N);

}


(2). 鎖的處理


不同的 mini-transaction 如何互斥?

在運算元據前,會根據鎖型別,加不同型別的鎖,之後將object和鎖型別存入m_memo:


mtr_memo_push(mtr, object, type);

commit完成之後呼叫release_latches(RELEASE_ALL)將資料上的鎖釋放.


(3). mini-transaction 插入資料


byte *mlog_open(mtr_t *mtr, ulint size): 開啟mtr的m_log


mlog_write_initial_log_record_low()函式向m_log中寫入type,space id,page no,並增加m_n_log_recs的數量


mtr->get_log()->push()按不同的型別寫資料


mlog_close(): 更新m_log中的位置


(4). mini-transaction 的 commit 過程

commit過程將mini-transaction的m_log資料拷貝到Redo Log Buffer中. 

將m_state設定為MTR_STATE_COMMITTING後,呼叫mtr_t::Command::execute()。


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/25380026/viewspace-2747128/,如需轉載,請註明出處,否則將追究法律責任。

相關文章