1. 程式人生 > >敲黑板:InnoDB的Double Write,你必須知道

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

>世界上最快的捷徑,就是腳踏實地,本文已收錄【[架構技術專欄](https://www.jiagoujishu.com)】關注這個喜歡分享的地方。 ## 前序 InnoDB引擎有幾個重點特性,為其帶來了更好的效能和可靠性: - 插入緩衝(Insert Buffer) - 兩次寫(Double Write) - 自適應雜湊索引(Adaptive Hash Index) - 非同步IO(Async IO) - 重新整理鄰接頁(Flush Neighbor Page) 今天我們的主題就是 `兩次寫(Double Write)`, 先一句話概括下: 上一次我們講過Insert Buffer 是用來提高儲存引擎效能上的提升,Double Write 就是為了在資料庫崩潰恢復時保證資料不丟失的一個重要特性,保證了資料的可靠性。 ## 概念點 ![](https://imgkr2.cn-bj.ufileos.com/23dbb4be-a10c-4bae-8240-98abfd722444.jpg?UCloudPublicKey=TOKEN_8d8b72be-579a-4e83-bfd0-5f6ce1546f13&Signature=pdM8jwNzA1nfDgTo24G7WoO8Suo%253D&Expires=1604481999) 如圖,還是先來說幾個基礎的概念: - 資料庫表空間由段(segment)、區(extent)、頁(page)組成 - 預設情況下有一個共享表空間ibdata1,如使用了innodb_file_per_table則每張表獨立表空間(指存放資料、索引、插入緩衝bitmap頁) - 段包括了資料段(B+樹的葉子結點)、索引段、回滾段 - 區,由連續的頁組成,任何情況下每個區都為1M,一個區中有64個連續頁(16k) - 頁,資料頁(B-tree Node)預設大小為16KB - 檔案系統一頁 預設大小為4KB - 碟片被分為許多扇形的區域,每個區域叫一個扇區,硬碟中每個扇區的大小固定為512位元組 - 髒頁,當資料從磁碟載入到緩衝池的資料頁後,資料頁內容被修改後,此資料頁稱為髒頁 ## 出現的問題 通過上次講的 [重要,知識點:InnoDB的插入緩衝](https://mp.weixin.qq.com/s/6t0_XByG8-yuyB0YaLuuBA) 我們知道,髒頁會在某些場景下進行刷盤,將緩衝池內的髒頁資料落地到磁碟。 因為儲存引擎緩衝池內的資料頁大小預設為16KB,而檔案系統一頁大小為4KB,所以在進行刷盤操作時,就有可能發生如下場景: ![](https://imgkr2.cn-bj.ufileos.com/a7872af6-9003-4cea-a472-16f4f23235f4.jpg?UCloudPublicKey=TOKEN_8d8b72be-579a-4e83-bfd0-5f6ce1546f13&Signature=lru0X6h4Yx%252Bk95rbPGBAcn9MKAA%253D&Expires=1604481927) 如圖所示,資料庫準備重新整理髒頁時,需要四次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 架構 ![](https://imgkr2.cn-bj.ufileos.com/965234e5-63a1-46b4-a3ab-7dd9d036ad90.jpg?UCloudPublicKey=TOKEN_8d8b72be-579a-4e83-bfd0-5f6ce1546f13&Signature=mQdXlxDMdQXT1LqyYEMxpSwxzAo%253D&Expires=1604481938) 如圖,其實Double Write 分為了兩個組成部分: - 記憶體中的double write buffer - 物理磁碟上共享表空間中連續的128個頁,即2個區(extent),大小同樣為2MB 可以看出,有了Double write後的髒頁重新整理流程就是多了幾步操作: 1. 在對緩衝池的髒頁進行重新整理時,並不直接寫磁碟,而是會通過memcpy函式將髒頁先複製到記憶體中的Double write buffer 2. 通過Double write buffer再分兩次,每次1MB順序地寫入共享表空間的物理磁碟上,然後馬上呼叫fsync函式,同步磁碟,避免緩衝寫帶來的問題 #### Double write崩潰恢復 ![](https://imgkr2.cn-bj.ufileos.com/6fbea747-42a0-440b-8b61-6138311f1a98.jpg?UCloudPublicKey=TOKEN_8d8b72be-579a-4e83-bfd0-5f6ce1546f13&Signature=7i4if0SCacsnauE5A87KeXs30Jo%253D&Expires=1604481946) 如圖,如果作業系統在將頁寫入磁碟的過程中發生了崩潰,在恢復過程中,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。 ![](https://oscimg.oschina.net/oscnet/up-0e46be065acfbfd144fd3378af09573a