Redo 日誌如何在關鍵時刻拯救資料庫?

語言: CN / TW / HK

​對於這樣的劇情,想必大家不會陌生:美國大片中拯救世界的英雄,平時看起來跟普通人沒啥區別,甚至還可能會有點讓人看不上。

但是,關鍵時刻,卻能爆發出驚人能量,挽狂瀾於既倒,扶大廈於將傾,拯救世界於危難之中。

今天我們要聊的主角:Redo 日誌,也是這樣的平民英雄。

本來 InnoDB 接收到插入、修改、刪除這樣的 DML 語句,以及建立表 & 索引、修改表結構這樣的 DDL 語句,修改 Buffer Pool 中的資料頁之後就完事了。

因為要保證資料不丟失,事情就變得複雜了,修改了資料頁不算完,還要生成 Redo 日誌,生成了也不算完,還要把它寫入 Redo 日誌檔案。

為了方便描述,本文後面會把 Redo 日誌檔案簡稱為日誌檔案。

通過以上描述,相信大家能夠發現,生成 Redo 日誌並寫入日誌檔案,顯然是額外操作,會額外消耗資源。

不惜額外消耗寶貴的伺服器資源都要儲存下來的東西,肯定不能是個繡花枕頭,那這個有用的枕頭什麼時候能派上用場呢?

當然是伺服器累了想小憩一下(突然崩潰)的時候了。

伺服器也不容易,誰還沒有個突然崩潰的時候呢?

說了這麼多,是時候確定 Redo 日誌的歷史地位了:Redo 日誌,在太平日子裡,不但是個雞肋,更是個累贅,但是,別把它不當英雄,關鍵時刻還得靠它拯救資料庫。

飯前甜點到此為止,接下來是正餐。

本文內容基於 MySQL 8.0.29 原始碼。

一、概述

MySQL 8.0 以前,Redo 日誌是序列寫入 log buffer 的,多個使用者執行緒想要同時往 log buffer 裡寫日誌,那是不行的,必須排隊等待(獲取互斥鎖),拿到互斥鎖之後,才能往 log buffer 裡寫日誌。

MySQL 8.0 中,序列寫入變為並行寫入,log buffer 由鄉間小道變成了單向 8 車道的高速公路,多個使用者執行緒可以同時往 log buffer 裡寫入 Redo 日誌,效率大大提升。

Redo 日誌從產生到刷盤,一共會經歷 4 個階段(產生、寫 log buffer、寫日誌檔案、刷盤),本文會用 4 個小節分別介紹這 4 個階段。

二、Redo 日誌產生

以一條非常簡單的插入語句為例,這個語句包含自增列,並且只插入一條記錄,我們假設插入過程中不會造成索引頁分裂,也不會產生溢位頁。

不考慮 Undo 日誌產生的 Redo 日誌,這樣一條 SQL 語句會包含 2 條 Redo 日誌(這 2 條日誌會形成一個日誌組):

  • 一條日誌中儲存著表中自增列的最大值(MySQL 8.0 把自增列的值持久化了)。
  • 另一條日誌中儲存著插入記錄各欄位的值。

每條日誌中還有可能會包含 InnoDB 需要的其它資訊。

插入記錄的過程中,會先產生一條 Redo 日誌用於記錄表中自增列的最大值,然後插入記錄,再產生另一條 Redo 日誌。

Redo 日誌並不會每產生一條就馬上寫入 log buffer,而是一組 Redo 日誌攢到一起往 log buffer 裡寫。

問題來了,產生了一條 Redo 日誌不能馬上寫入 log buffer,那怎麼辦?

那就需要有一個地方臨時存放日誌組中不同時間點產生的日誌了,這個地方就是 mtr 中的 m_log 連結串列。

m_log 連結串列是由一個一個 block 組成的連結串列,block 大小為 512 位元組,每產生一條日誌,就追加到 m_log 的 block 中,如果一個 block 寫滿了,就再申請一個 block 接著寫。

那 mtr 又是個啥?

mtr 是 Mini-Transaction 的縮寫,是一組不可分隔的操作組成的一個整體,就像前面插入語句的例子中,儲存表中自增列的最大值和插入記錄就是一組不可分隔的操作,必須放入一個 mtr。

兩個操作放入一個 mtr,它們的日誌也就放在同一個 mtr 中了。這樣就能保證兩個操作產生的 Redo 日誌一起寫入 log buffer 和日誌檔案中。

mtr 的用途可不止打包一組 Redo 日誌這麼簡單,它還會對 SQL 執行過程中 mtr 需要訪問的 Buffer Pool 中的頁加鎖、修改頁中的資料、釋放鎖,本文我們只介紹 Redo 日誌,對於 mtr 就不再展開了。

還有一個概念需要解釋一下,日誌組就是一個 mtr 中的所有日誌。

三、寫入 log buffer

mtr 中一組不可分隔的操作都完成之後,就該提交了,mtr 提交過程中要乾的第一件事就是把它裡面臨時存放的一組 Redo 日誌寫入到 log buffer 中。

一個事務中可能會包含多個 mtr,mtr 的提交和事務的提交不是一個概念,不要混淆。

前面說到在 MySQL 8.0 中,往 log buffer 裡寫日誌不需要排隊等待(獲取互斥鎖),多個使用者執行緒可以同時寫入。

這個無鎖化設計是通過在 log buffer 中為每個 mtr 中的 Redo 日誌預留空間實現的,每個 mtr 都有一段屬於自己的空間,各自往自己專屬的空間內寫入日誌,相互之間就不影響了。

使用者執行緒的 mtr 往 log buffer 寫 Redo 日誌前,會先獲取一段序列號。

以當前系統中已產生的最大序列號(SN)作為 start_sn,加上本次要往 log buffer 中寫入的 Redo 日誌的位元組數(len),得到 end_sn(end_sn = start_sn + len)。

start_sn ~ end_sn 就是本次要寫入 log buffer 的 Redo 日誌的序列號區間。

獲取 start_sn、end_sn 的過程是原子操作,多個執行緒之間不會出現衝突,不會獲取到有交叉的序列號區間。

拿到 start_sn ~ end_sn 只是第一步,還需要進行一次轉換,把序列號(SN)轉換為日誌序列號(LSN),得到一個 LSN 的範圍:start_lsn ~ end_lsn,這個範圍對應著 log_buffer 中為 mtr 即將寫入的 Redo 日誌預留的空間。

SN 是截止某個時刻,InnoDB 中實際產生的 Redo 日誌位元組數。

SN 按照 496 位元組拆分,拆分後每 496 位元組,加上 12 位元組的頭資訊、4 位元組尾部檢驗碼,得到 512 位元組的 block,經過這樣的轉換之後,得到的數字就是 LSN。

至此,寫入日誌到 log buffer 的準備工作又往前推進了一步。

但是,彆著急,也許還要再等等,如果 log buffer 中剩餘空間不夠寫入當前 mtr 的 Redo 日誌,那就需要等到 log buffer 中的 Redo 日誌被寫入日誌檔案,為當前 mtr 的 Redo 日誌騰出空間才行。

這裡的寫入日誌檔案,只是呼叫了作業系統的寫檔案方法,把 Redo 日誌寫入日誌檔案的作業系統緩衝區中,日誌檔案暫時還不會重新整理到磁碟上。

那怎麼判斷 log buffer 中是否有空間呢?

要回答這個問題,我們需要先介紹一個屬性 log_sys.write_lsn,表示 LSN 小於 log_sys.writen_lsn 的日誌都已經寫入到日誌檔案緩衝區中。

end_sn <= log_sys.write_lsn + innodb_log_buffer_size(預設 16M),就表示 log buffer 中有空間寫入當前 mtr 的 Redo 日誌。

如果要等,總不能一直等吧,等到什麼時候是個頭呢?

如果需要等待,使用者執行緒會監聽 log.write_events 事件,log buffer 中有空間寫入 Redo 日誌之後,當前使用者執行緒會收到事件通知。

誰會給這些等待的使用者執行緒傳送事件通知呢?後面會有介紹,請繼續往下看。

等到 log buffer 中有空間之後,往裡面寫入日誌就很簡單了,直接把 mtr 中的 Redo 日誌拷貝到 log buffer 中就完事了。

寫完之後,還需要根據 mtr 的 start_lsn 在 recent_written.m_links 中找到對應的 SLOT,然後把 mtr 的 end_lsn 寫入這個 SLOT,表示這個 mtr 已經把它的全部 Redo 日誌寫入 log buffer 了。

如果根據 start_lsn 在 recent_written.m_links 中找到的 SLOT 正在被其它 mtr 使用,當前這個使用者執行緒會採用迴圈 + 間隔休眠 20 微秒的方式,直到 SLOT 可以使用。

前面兩段涉及到 recent_written 的介紹,大家看了可能會覺得一頭霧水,先不要著急,有個模糊印象就行。因為這兩段邏輯是在寫日誌到 log buffer 這個階段發生的,所以這裡必須要提一下露個臉,相當於佔個位,詳細介紹在第四部分。

說完了寫入 Redo 日誌到 log buffer,我們回到使用者執行緒等待 log buffer 中有空間寫入它的 Redo 日誌,這個等待過程是個躺平的過程,在這個過程中,使用者執行緒除了等待事件通知,其它事情啥也不幹。

在使用者執行緒看來,等待的過程中歲月靜好,但是,世上本來沒有歲月靜好,它感受到的歲月靜好,無非是因為有人替它負重前行。

誰在負重前行?

那是另一個默默工作的執行緒,它的名字叫作 log_writer,它是一個搬運工,一個專門把 log buffer 中的 Redo 日誌寫入到日誌檔案的執行緒。

log_writer 執行緒只調用作業系統寫檔案方法,把 Redo 日誌寫入日誌檔案,不會重新整理到磁碟上,此時,Redo 日誌還在日誌檔案的作業系統緩衝區中。

接下來,就是 log_writer 執行緒的主場了。

四、寫入日誌檔案

log writer 執行緒把 log buffer 中的 Redo 日誌寫入日誌檔案緩衝區,寫入的這一段 Redo 日誌必須是連續的,中間不能出現空洞。

上一個步驟中,不同使用者執行緒可以並行把各自 mtr 中的 Redo 日誌寫入 log buffer 中,解決了寫入速度慢的問題,同時也帶來了新問題。

不同使用者執行緒的 Redo 日誌量可能不一樣,有的執行緒會先寫完,有的執行緒後寫完,如果某一個範圍內,頭部的日誌寫完了,尾部的日誌也寫完了,中間的日誌還沒寫完,這就出現了空洞。

舉個例子,假設有 3 個不同的使用者執行緒,各有一個 mtr 要提交,我們把這 3 個使用者執行緒的 mtr 分別叫作 mtr 10、mtr 11、mtr 12。

  • mtr 10 的 Redo 日誌佔用 200 位元組,LSN 範圍是 start_lsn(2097252) ~ end_lsn(2097452)。
  • mtr 11 的 Redo 日誌佔用 12045 位元組,LSN 範圍是 start_lsn(2097452) ~ end_lsn(2109497)。
  • mtr 12 的 Redo 日誌佔用 300 位元組,LSN 範圍是 start_lsn(2109497) ~ end_lsn(2109797)。

每一個 mtr 的 end_lsn 其實是不屬於它的,而是屬於下一個 mtr,是下一個 mtr 的 start_lsn。所以,每個 mtr 的 LSN 範圍是一個左閉右開區間,例如:mtr 10 [2097252, 2097452)。

mtr 10、mtr 12 的日誌比較小,mtr 11 的日誌比較大,可能會存在這樣的情況,mtr 10、mtr 12 的日誌都已經全部寫入 log buffer,mtr 11 的日誌只有一部分寫入了 log buffer,中間是存在空洞的。

因為存在空洞,log_writer 執行緒不能把 mtr 10 ~ 12 的 Redo 日誌都寫入日誌檔案,只能把 mtr 10 的 Redo 日誌寫入日誌檔案。

等到 mtr 11 的 Redo 日誌全部寫入 log buffer 之後,才能把 mtr 11 ~ 12 的 Redo 日誌一起寫入日誌檔案。

那它怎麼知道截止到哪個位置的日誌是連續的,可以寫入日誌檔案的呢?

也許我們都能很快想到用一個變數把這個位置記錄下來就好了。

沒錯,InnoDB 也是這麼幹的,全域性日誌物件(log_sys)中,有一個 recent_written 屬性,這個屬性也是個物件,它有一個屬性 m_tail(log_sys.recent_written.m_tail),用來記錄 log buffer 中小於哪個 LSN 的日誌都是連續的。

知道了用什麼記,現在有個關鍵問題,那就是怎麼記?

recent_written 物件,有個屬性 m_links(recent_written.m_links),這是個陣列,預設有 1048576 個元素,每個元素是一個 SLOT,每個 SLOT 佔用 8 位元組,總共佔用 8M 記憶體空間。

m_links 的每個 SLOT 對應 log buffer 中的一個 LSN,每個使用者執行緒的 mtr 往 log buffer 中寫入它的全部 Redo 日誌之後,會根據 start_lsn 在 m_links 中找到一個 SLOT,並把 end_lsn 寫入這個 SLOT。

還是以前面的 mtr 10 ~ 12 為例,當 mtr 10 把它的所有 Redo 日誌全部寫入 log buffer 之後,根據 start_lsn(2097252) 找到對應的 SLOT 並寫入 end_lsn(2097452)。

SLOT 下標 = start_lsn(2097252) % SLOT 數量(1048576) = 100。

m_links[100] = end_lsn(2097452),m_links[101 ~ 299] 對應著 LSN 2097253 ~ 2097451,也屬於 mtr 10 的範圍,不過這個區間只是用來佔位的,mtr 10 並不會往其中的 SLOT 寫入 LSN。

重要說明:實際上,因為 m_links 被當作環形結構迴圈、重複使用,每個 SLOT 都有可能曾經被其它 mtr 寫入過 end_lsn。

對於 mtr 10 來說,除了 start_lsn 對應的 SLOT(m_links[100])的值是 end_lsn(2097452) 之外,其它 SLOT(m_links[101 ~ 299])的值可能是 0,也可能是之前的某個 mtr 寫入的 end_lsn。

如果 SLOT 的值是之前的某個 mtr 寫入的 end_lsn,這個 end_lsn 一定是小於等於 mtr 10 的 start_lsn 的。

當 mtr 12 把它的所有 Redo 日誌全部寫入 log buffer 之後,根據 start_lsn(2109497) 找到對應的 SLOT 並寫入 end_lsn(2109797)。

SLOT 下標 = start_lsn(2109497) % SLOT 數量(1048576) = 12345。

m_links[12345] = end_lsn(2109797),m_links[12346 ~ 12644] 對應著 LSN 2109498 ~ 2109796,也屬於 mtr 12 的範圍,這個區間內 SLOT 的值可能為 0 或者小於等於 start_lsn(2109497) 的數字(具體原因可以參照 mtr 10 的說明)。

此時,mtr 11 的 Redo 日誌還沒有全部寫入 log buffer,m_links[300 ~ 12344] 對應著 LSN 2097452 ~ 2109496,屬於 mtr 11 的範圍,這個區間內 SLOT 的值可能為 0 或小於等於 start_lsn(2097452) 的數字(具體原因可以參照 mtr 10 的說明)。

說完了 mtr 10 ~ 12 的狀態,接下來就要正式介紹 Redo 日誌寫入日誌檔案的關鍵步驟了:根據 recent_written.m_links 找到 log buffer 中連續的日誌區間。

先來回憶一下:

  • recent_written.m_tail,表示 log buffer 中小於 recent_written.m_tail 的日誌都是連續的。
  • log_sys.write_lsn, 表示 log buffer 中小於 log_sys.write_lsn 的日誌都已經寫入日誌檔案了。

假設,此時 recent_written.m_tail = 2097252,這是 mtr 10 的 start_lsn,表示 mtr 10 之前的 mtr 往 log buffer 中寫入的 Redo 日誌已經是連續的了。

log_writer 執行緒接下來從 m_tail 對應的 LSN(2097252)開始,尋找更大範圍的連續日誌區間。

計算 m_tail 對應的 SLOT 下標 = m_tail(2097252) % SLOT 數量(1048576) = 100。

讀取 SLOT 100(下標為 100 的 SLOT)的值,得到 2097452,這是 mtr 10 的 end_lsn,也是 mtr 11 的 start_lsn,說明 mtr 10 的日誌已寫入 log buffer。

LSN < 2097452 的區間,Redo 日誌都是連續的了,更新 m_tail 的值,recent_written.m_tail = 2097452。

繼續尋找,計算 m_tail 對應的 SLOT 下標 = m_tail(2097452) % SLOT 數量(1048576) = 300。

讀取 SLOT 300 的值,得到 0,說明 mtr 11 還沒有把 Redo 日誌全部寫入 log buffer 了,本次尋找更大範圍的連續日誌區間結束,m_tail 保持為 2097452 不變。

log_writer 執行緒可以把 log buffer 中 LSN < m_tail(2097452) 的 Redo 日誌寫入到日誌檔案,寫完之後,更新 log_sys.write_lsn 的值,log_sys.write_lsn = 2097452。

然後,log_writer 執行緒或 log_write_notifier 執行緒會通知正在等待往 log buffer 中 LSN < m_tail(2097452) 這個區間寫 Redo 日誌的使用者執行緒,告訴它們可以寫 Redo 日誌了。

為了減輕 log_writer 執行緒的負擔,通知使用者執行緒這個邏輯做了區分:

如果只有一個使用者執行緒正在等待往 log buffer 中 LSN < m_tail(2097452) 區間寫 Redo 日誌,log_writer 執行緒順手就通知這個使用者執行緒了。

如果有多個使用者執行緒正在等待往  log buffer 中 LSN < m_tail(2097452) 區間寫 Redo 日誌,log_writer 執行緒會讓 log_write_notifier 執行緒去通知等待這個範圍可寫的所有使用者執行緒。

第三部分說過,如果使用者執行緒需要等待 log buffer 中有空間寫入它的 Redo 日誌,這個使用者執行緒會監聽 log.write_events 事件,log_writer & log_write_notifier 執行緒就是通過這個事件通知使用者執行緒的。

實際上,使用者執行緒監聽的是 log.write_events[slot],slot 是對 mtr 的 start_lsn 取模計算得到的,計算公式是這樣的:slot = start_lsn % recent_written.m_links 的 SLOT 數量(預設 1048576)。

監聽到具體的 slot 上是為了保證每個使用者執行緒只會接收到 log.write_events 事件中和自己有關的通知。

過了一小會,log_writer 執行緒又要開始工作了,此時,mtr 11 中的全部 Redo 日誌都寫入 log buffer 了。

上次結束時,recent_written.m_tail = 2097452,其對應的 SLOT 下標為 300,這次從 SLOT 300 開始繼續尋找。

讀取 SLOT 300 的值,得到 2109497,這是 mtr 11 的 end_lsn,也是 mtr 12 的 start_lsn,說明 LSN < 2109497 的區間,Redo 日誌都是連續的了,更新 m_tail 的值,recent_written.m_tail = 2109497。

繼續尋找,計算 m_tail 對應的 SLOT 下標 = m_tail(2109497) % SLOT 數量(1048576) = 12345。

讀取 SLOT 12345 的值,得到 2109797,這是 mtr 12 的 end_lsn,也是 mtr 12 之後的下一個 mtr 的 start_lsn,說明 LSN < 2109797 的區間,Redo 日誌都是連續的了,更新 m_tail 的值, recent_written.m_tail = 2109797。

繼續尋找,計算 m_tail 對應的 SLOT 下標 = m_tail(2109797) % SLOT 數量(1048576) = 12645。

讀取 SLOT 12645 的值,得到 0,說明 Redo 日誌連續的區間到這裡暫時結束,m_tail 保持為 2109797 不變。

log_writer 執行緒可以把 log buffer 中 LSN < m_tail(2109797) 的 Redo 日誌寫入到日誌檔案了,寫完之後,更新 log_sys.write_lsn 的值,log_sys.write_lsn = 2109797。

然後,log_writer 執行緒或 log_write_notifier 執行緒會觸發 log.write_events 事件,通知正在等待往 LSN < m_tail(2109797) 區間內寫 Redo 日誌的使用者執行緒,告訴它們可以寫 Redo 日誌了。

五、日誌檔案刷盤

Redo 日誌從 log buffer 寫入日誌檔案中,並不是直接就寫到磁碟檔案中了,而是會先進入日誌檔案在作業系統的緩衝區中,還需要經過刷盤操作才能最終寫到磁碟上的日誌檔案中,成為持久化的日誌。

Redo 日誌檔案刷盤,也是由專門的執行緒完成的,這個執行緒是 log_flusher。

log_flusher 執行緒的常規工作是大概每秒執行一次刷盤操作。

全域性日誌物件(log_sys)中有一個屬性 flushed_to_disk_lsn 表示小於 log_sys.flushed_to_disk_lsn 的 Redo 日誌都已經重新整理到磁碟上的日誌檔案中了。

前面我們還提到了另一個屬性 log_sys.write_lsn,表示 log buffer 中小於 log_sys.write_lsn 的日誌都已經寫入日誌檔案了。

每次執行刷盤操作時,對比這兩個屬性的值,就能判斷出來日誌檔案緩衝區中是不是有新的 Redo 日誌需要刷盤。

如果 log_sys.write_lsn 大於 log_sys.flushed_to_disk_lsn,說明需要刷盤,否則本次不需要執行刷盤操作,log_flusher 執行緒可以愉快的躺平大概 1s 左右,然後等待下一次時間到了,再進行同樣的邏輯判斷,確定是否需要刷盤。

不出意外的話,log_flusher 執行緒就是這麼簡單平凡,日復一日,年復一年的機械單調的工作著。

但是,這顯然不符合劇情發展,單調的故事中總是會時不時出現點刺激的劇情。

log_flusher 執行緒除了常規的每秒執行一次刷盤操作,還會監聽一個事件:log.flusher_event,通過這個事件和外界保持聯絡,接受外部刺激。

我們來看一個帶給 log_flusher 執行緒刺激場景:

innodb_flush_log_at_trx_commit = 1 時,事務每次提交的時候,都心急火燎的,不可能心平氣和的等著 log_flusher 每秒執行一次刷盤操作,必須讓 log_flusher 立馬起來幹活(事務會觸發 log.flusher_event 事件),把事務中產生的 Redo 日誌刷盤,然後,事務才能向客戶端交差。

innodb_flush_log_at_trx_commit = 2 時,事務心急火燎的物件就不是 log_flusher 執行緒了,而是 log_writer 執行緒,因為這種場景下,事務只需要等待 log_writer 執行緒把它的 Redo 日誌寫入日誌檔案緩衝區就可以了,不需要等待刷盤。

事務催促 log_flusher 執行刷盤操作之後,會等待刷盤操作完成。等待過程是通過監聽 log.flush_events[slot] 事件實現的。

slot 是對事務中最後一個 mtr(一個事務可以包含多個 mtr)的 end_lsn 取模計算得到的,計算公式是這樣的:slot = end_lsn % recent_written.m_links 的 SLOT 數量(預設 1048576)。

slot 的作用是保證每個使用者執行緒只會接收到 log.flush_events 事件中和自己有關的通知。

刷盤操作完成後,log_flusher 執行緒或 log_flush_notifier 執行緒會通知正在等待 LSN < m_tail(2097452) 這個區間內的 Redo 日誌刷盤的使用者執行緒。

為了減輕 log_flusher 執行緒的負擔,通知使用者執行緒這個邏輯做了區分:

如果只有一個使用者執行緒正在等待本次刷盤結果,log_flusher 執行緒順手就通知這個使用者執行緒了。

如果有多個使用者執行緒正在等待本次刷盤結果,log_flusher 執行緒會讓 log_flush_notifier 執行緒去通知等待本次刷盤結果的所有使用者執行緒。

六、總結

Redo 日誌是以日誌組為單位寫入 log buffer 和日誌檔案的,每個日誌組的 Redo 日誌都來源於一個 mtr。

多個使用者執行緒的 mtr 以無鎖的方式並行往 log buffer 裡寫入 Redo 日誌,只需要寫入之前計算出來 mtr 中 Redo 日誌的 LSN 範圍,通過這個 LSN 範圍在 log buffer 中鎖定一段區間,多個使用者執行緒鎖定的區間不一樣,不會出現衝突。

log_writer 執行緒把已經寫入 log buffer 的 Redo 日誌寫入日誌檔案,需要保證 Redo 日誌是連續的,InnoDB 用 log_sys.recent_written 物件中的 m_links 陣列、m_tail 屬性來輔助 log_writer 執行緒找到連續的日誌區間。

log_writer 執行緒把 log buffer 中的 Redo 日誌寫入日誌檔案之後,會通知等待 log buffer 為它騰出空間的使用者執行緒,或者讓 log_write_notifier 執行緒通知使用者執行緒。

log_flusher 執行緒每秒執行一次刷盤操作,同時還監聽了 log.flusher_event 事件,用於接收外部刺激,觸發它在週期性刷盤工作的時候也能夠更及時的刷盤。

如果 log_sys.write_lsn 大於 log_sys.flushed_to_disk_lsn 說明需要執行刷盤操作,否則不需要。

log_flusher 執行緒執行完刷盤操作之後,也會通知等待刷盤操作完成的使用者執行緒,或者讓 log_flush_notifier 執行緒通知使用者執行緒。

最後,放上一張整體流程圖,希望能夠有助於大家理解 Redo 日誌刷盤的整體流程。