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

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

前序

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。

Tags: