敲黑板:InnoDB的Double Write,你必須知道

架構技術專欄發表於2020-11-03

世界上最快的捷徑,就是腳踏實地,本文已收錄【架構技術專欄】關注這個喜歡分享的地方。

前序

InnoDB引擎有幾個重點特性,為其帶來了更好的效能和可靠性:

  • 插入緩衝(Insert Buffer)

  • 兩次寫(Double Write)

  • 自適應雜湊索引(Adaptive Hash Index)

  • 非同步IO(Async IO)

  • 重新整理鄰接頁(Flush Neighbor Page)

今天我們的主題就是 兩次寫(Double Write), 先一句話概括下:

上一次我們講過Insert Buffer 是用來提高儲存引擎效能上的提升,Double Write 就是為了在資料庫崩潰恢復時保證資料不丟失的一個重要特性,保證了資料的可靠性。

概念點

如圖,還是先來說幾個基礎的概念:

  • 資料庫表空間由段(segment)、區(extent)、頁(page)組成

  • 預設情況下有一個共享表空間ibdata1,如使用了innodb_file_per_table則每張表獨立表空間(指存放資料、索引、插入緩衝bitmap頁)

  • 段包括了資料段(B+樹的葉子結點)、索引段、回滾段

  • 區,由連續的頁組成,任何情況下每個區都為1M,一個區中有64個連續頁(16k)

  • 頁,資料頁(B-tree Node)預設大小為16KB

  • 檔案系統一頁 預設大小為4KB

  • 碟片被分為許多扇形的區域,每個區域叫一個扇區,硬碟中每個扇區的大小固定為512位元組

  • 髒頁,當資料從磁碟載入到緩衝池的資料頁後,資料頁內容被修改後,此資料頁稱為髒頁

出現的問題

通過上次講的 重要,知識點:InnoDB的插入緩衝 我們知道,髒頁會在某些場景下進行刷盤,將緩衝池內的髒頁資料落地到磁碟。

因為儲存引擎緩衝池內的資料頁大小預設為16KB,而檔案系統一頁大小為4KB,所以在進行刷盤操作時,就有可能發生如下場景:

如圖所示,資料庫準備重新整理髒頁時,需要四次IO才能將16KB的資料頁刷入磁碟。

但當執行完第二次IO時,資料庫發生意外當機,導致此時才刷了2個檔案系統裡的頁,這種情況被稱為寫失效(partial page write)。

此時重啟後,磁碟上就是不完整的資料頁,就算使用redo log也是無法進行恢復的。

注意

  • redo log無法恢復資料頁損壞的問題,恢復必須是資料頁正常並且redo log正常。

  • 這裡要知道一點,redo log中記錄的是對頁的物理操作,如偏移量600,寫'xxxx'記錄。

  • 如果這個頁本身已經發生了損壞,再對其進行重做是沒有意義的

該怎麼解決這個問題

那應該怎麼來解決這個問題呢?其實大家想一下就會有個大概的答案,就是給它搞個備份唄。

如果寫髒頁的時候發生當機,在重啟後使用下備份先恢復下資料頁在寫磁碟就可以了,其實這就是Double Write

Double Write 出現

千呼萬喚始出來,為了防止我們可憐的資料被破壞,InnoDB儲存引擎提供了重要的Double Write 特性,避免了資料丟失的慘劇發生。

下面我們來慢慢的來看看Double Write 到底是怎麼提高可靠性的

Double Write 解決的問題

在資料庫進行髒頁重新整理時,如果此時當機,有可能會導致磁碟資料頁損壞,丟失我們重要的資料。此時就算重做日誌也是無法進行恢復的,因為重做日誌記錄的是對頁的物理修改。

其實就是在重做日誌前,使用者需要一個頁的副本,當寫入失效發生時,先通過頁的副本來還原該頁,再進行重做,這就是double write。

Double Write 架構

如圖,其實Double Write 分為了兩個組成部分:

  • 記憶體中的double write buffer
  • 物理磁碟上共享表空間中連續的128個頁,即2個區(extent),大小同樣為2MB

可以看出,有了Double write後的髒頁重新整理流程就是多了幾步操作:

  1. 在對緩衝池的髒頁進行重新整理時,並不直接寫磁碟,而是會通過memcpy函式將髒頁先複製到記憶體中的Double write buffer

  2. 通過Double write buffer再分兩次,每次1MB順序地寫入共享表空間的物理磁碟上,然後馬上呼叫fsync函式,同步磁碟,避免緩衝寫帶來的問題

Double write崩潰恢復

如圖,如果作業系統在將頁寫入磁碟的過程中發生了崩潰,在恢復過程中,InnoDB儲存引擎可以從共享表空間中的Double write中找到該頁的一個副本,將其複製到表空間檔案,再應用重做日誌。

下面顯示了一個由Double write進行恢復的情況:

090923 12:36:32 mysqld restarted
090923 12:26:33 InnoDB: Database was not shut down normally!
InnoDB: Starting crash recovery.
InnoDB: Reading tablespace information from the .ibd files...
InnoDB: Crash recovery may have faild for some .ibd files!
InnoDB: Restoring possible half-written data pages from the doublewrite.
InnoDB: buffer...

Double Write 的問題

Double write buffer 它是在物理檔案上的一個buffer, 其實也就是file,所以它會導致系統有更多的fsync操作,而因為硬碟的fsync效能問題,所以也會影響到資料庫的整體效能。

Double write頁是連續的,因此這個過程是順序寫的,開銷並不是很大。

在完成Double write頁的寫入後,再將Double write buffer中的頁寫入各個資料檔案中,此時的寫入則是離散的

總結

  1. 當commit 一個修改語句時,如果redo log有空閒區域,直接寫redo log,如果redo log沒有空閒區域,那麼需要把被覆蓋的redo log對應的資料頁重新整理到data file 中,最後改pool buffer中的記錄

  2. innodb的redo log 不會記錄完整的一頁資料,因為這樣日誌太大,它只會記錄那次(sequence)如何操作了(update,insert)哪頁(page)的哪行(row)

  3. 因為資料庫使用的頁(page,預設16KB)大小和作業系統對磁碟的操作頁(page,預設4KB)不一樣,當提交了一個頁需要重新整理到磁碟,會有多次IO, 此時刷了前面的8k時異常發生當機。在系統恢復正常後,如果沒有double write機制,此時資料庫磁碟內的資料頁已損壞,無法使用redo log進行恢復。

  4. 如果有double write buffer,會檢查double writer的資料的完整性,如果不完整直接丟棄double write buffer內容,重新執行那條redo log,如果double write buffer的資料是完整的,用double writer buffer的資料更新該資料頁,跳過該redo log。

相關文章