讀《MySQL技術內幕:InnoDB存儲引擎(第2版)》學習筆記
InnoDB 存儲引擎體系架構
InnoDB存儲引擎主要由後臺線程、內存池、和磁盤存儲組成
後臺線程
InnoDB是多線程的模型 ,不同線程負責不同的任務,分爲Master thread、IO Thread、和Purge Thread。
Master thread
主要負責將內存中的緩存數據,刷新到磁盤,刷新策略:
每秒的操作包括:
- 重做日誌緩衝刷新到磁盤(重做日誌文件),即使這個事務還沒有提交
- 合併插入緩衝
- 至多刷新100個InnoDB的緩衝池中的髒頁到磁盤
每十秒的操作包括:
- 刷新100個髒頁到磁盤
- 合併至多5個插入緩衝
- 將重做日誌緩衝刷新到磁盤
- 執行 full purge 操作,刪除無用的undo頁
- 刷新100個或10個髒頁到磁盤
IO Thread
InnoDB 大量使用 AIO(Async IO)來處理寫 IO 請求,這樣可以提高數據庫的性能。IO Thread主要負責這些IO請求的回調處理。
Purge Thread
事務被提交後,其使用的undo log可能不再需要,因此需要Purge Thread回收已經使用並分配的undo頁。
內存池的管理
緩衝池
簡單來說,緩衝池就是一塊內存區域,通過內存來彌補磁盤速度較慢對數據庫性能的影響。
讀取操作,首先將從磁盤讀取到的頁存放到緩衝池中,下次讀取相同的頁時,首先判斷該頁是否在緩衝池中,若在,稱該頁在緩衝池中被命中,直接讀取緩衝池的該頁,否則,讀取磁盤。
修改操作,先修改緩衝池中的頁,然後以一定的頻率刷新到磁盤。而頁的刷新操作不是每次修改時觸發,而是通過 CheckPoint 的機制刷新回磁盤。
LRU List、Free List和 Flush List
緩衝池是一個很大的內存區域,其中存放各種類型的頁。InnoDB是怎麼管理這麼大的內存區域?
通常來說,數據庫中的緩衝池是通過 LRU 算法管理的。即最頻繁最近使用的頁在 LRU 列表的前端,而最少使用的頁在 LRU 列表的尾端。當緩衝池中不能存放新讀取到的頁時,將釋放LRU列表尾端的頁。
InnoDB在 LRU 列表中加入了 midpoint 位置,新讀取的頁,雖然是最近訪問的頁,但並不是直接放入到 LRU 列表的首部,而是放入到 LRU列表的midpoint位置,默認情況下是在 LRU列表長度的 5/8 處。因爲新讀取的數據,不一定就是最活躍的數據,不適合直接放到列表的首部。
LRU 列表用於管理已經讀取的頁。但數據庫剛剛啓動時,LRU列表是空的,這時,頁都在Free 列表中,當需要從緩衝池中分頁時,先從Free List 中查找是否有可用的空閒頁,若有,將該頁從 Free List中刪除,放入 LRU 列表中。否則,根據LRU,淘汰LRU末尾的頁,將該內存空間分配給新的頁。
在 LRU 列表中的頁被修改後,稱爲髒頁,即緩衝池中的頁和磁盤的頁數據產生了不一致。這時,數據庫會通過 Checkpoint機制將髒頁刷新回磁盤,而 Flush 列表中的頁即爲髒頁列表。需要注意的是,髒頁既存在於 LRU 列表中,也存在於 Flush 列表。LRU 列表用來管理緩衝池中頁的可用性,Flush用來管理將頁刷新回磁盤,二者不影響。
重做日誌緩衝
InnoDB首先將重做日誌先放入到這個緩衝區,然後按照一定的頻率將其刷新到磁盤上的重做日誌文件。默認大小爲 8 MB,以下三種情況,會對重做日誌進行刷新。
- Master Thread 每秒對重做日誌緩衝刷新
- 每個事務提交時,對重做日誌緩衝刷新
- 當重做日誌緩衝池剩餘空間小於 1/2 時
Checkpoint 技術
checkpoint的目的是將緩衝池中的髒頁刷回到磁盤,Checkpoint 技術解決以下幾個問題:
- 縮短數據庫的恢復時間
- 緩衝池不夠用時,將髒頁刷新回磁盤
- 重做日誌不可用時,刷新髒頁
什麼時間觸發checkpoint?
- 發生在數據庫關閉時將所有的髒頁刷新回磁盤,這是默認的工作方式
- Master Thread差不多以每秒或每十秒的速度從緩衝池中的髒頁列表中刷新一定比例的頁回磁盤
- 如果空閒頁小於100,那麼將LRU列表尾部的頁移除,如果這些頁中有髒頁,那麼需要 Checkpoint
- 髒頁太多,導致InnoDB 存儲引擎強制 Checkpoint,目的是爲了保證緩衝池有足夠可用的頁,默認爲 75%
雙寫
兩次寫可提高InnoDB存儲引擎數據頁的可靠性。當數據庫發生宕機時,可能InnoDB正在寫入某個頁到表中,而這個頁只寫了一部分,之後就發生了宕機,這種情況被稱爲部分寫失效。如果發生寫失效,是不是可以通過重做日誌進行恢復呢?但需要知道的是,重做日誌中記錄的是對頁的物理操作,如偏移量800寫’qqq’記錄。如果這個頁本身發生了損壞,再對其進行重做是沒有意義的。
doublewrite有兩部分構成,一部分是內存中的 doublewrite buffer,大小爲 2MB,另一部分是物理磁盤上共享表空間中連續的128個頁,即兩個區(extent),大小同樣爲 2MB。在對緩衝池的髒頁進行刷新時,並不直接寫磁盤,而是會通過memcpy函數將髒頁先複製到內存的 doublewrite buffer,然後通過doublewrite buffer再分兩次,每次1MB順序寫入共享表空間的物理磁盤上,然後馬上調用fsync函數,同步磁盤,避免緩衝寫帶來的問題。
在以上的過程中,因爲doublewrite是連續的,因此這個過程是順序寫,開銷不是很大,完成doublewrite頁的寫入後,再將doublewrite buffer中的頁寫入各個表空間文件中,此時的寫是離散的。
如果數據庫在重啓的過程中,發現磁盤上表空間的數據頁發生了損壞,就會使用共享表空間中的副本,替換損壞的數據頁。
重做日誌redo log是不是也使用了雙寫的技術呢?沒有這個必要,因爲重做日誌刷新到磁盤的最小單位是一個扇區(大小是512B),操作系統能夠保證一個扇區的數據存儲到存盤的原子性。