一、控制塊+緩存頁
??Buffer Pool中默認的緩存頁大小和在磁盤上默認的頁大小是一樣的,都是16KB。為了更好的管理這些在Buffer Pool中的緩存頁,InnoDB為每一個緩存頁都創建了一些所謂的控制信息,這些控制信息包括該頁所屬的表空間編號、頁號、緩存頁在Buffer Pool中的地址、鏈表節點信息、一些鎖信息以及LSN信息,當然還有一些別的控制信息。
??每個緩存頁對應的控制信息占用的內存大小是相同的,稱為控制塊。控制塊和緩存頁是一一對應的,它們都被存放到Buffer Pool 中,其中控制塊被存放到Buffer Pool的前邊,緩存頁被存放到Buffer Pool后邊,所以整個Buffer Pool對應的內存空間看起來就是這樣的:
注:每個控制塊大約占用緩存頁大小的5%,而設置的innodb_buffer_pool_size并不包含這部分控制塊占用的內存空間大小,也就是說InnoDB在為Buffer Pool向操作系統申請連續的內存空間時,這片連續的內存空間一般會比innodb_buffer_pool_size的值大5%左右。
二、Free鏈表管理
MySQL服務在剛啟動的時候,需要完成對Buffer Pool的初始化,也就是向操作系統申請Buffer Pool的存儲空間,然后它們劃分為若干對控制塊和緩存頁。
但此時還沒有使用,所以Buffer Pool中還沒有真實的頁數據,隨著程序運行,就會慢慢有磁盤頁數據被緩存在Buffer Pool中。
對于InnoDB來說,將磁盤頁讀取到Buffer Pool中有幾個關鍵問題,即哪些緩存頁是空閑,哪些緩存頁已經被使用了。
緩存頁對應的控制塊就起了大作用,可以把所有空閑的緩存頁對應的控制塊作為一個節點放到一個鏈表中,這個鏈表也可以被稱作free鏈表,或者說空閑鏈表。
剛完成初始化的Buffer Pool中所有的緩存頁都是空閑的,所以每一個緩存頁對應的控制塊都會被加入到free鏈表中,free鏈表的效果圖就是這樣的:
有了這個free鏈表之后,每當需要從磁盤中加載一個頁到Buffer Pool中時,就從free鏈表中取一個空閑的緩存頁,并且把該緩存頁對應的控制塊的信息填上(就是該頁所在的表空間、頁號之類的信息),然后把該緩存頁對應的free鏈表節點從鏈表中移除,表示該緩存頁已經被使用了。
三、緩存頁的哈希處理
當訪問某個頁的數據時,如何知道該頁已經在Buffer Pool中了呢?
InnoDB根據表空間號 + 頁號來定位一個頁,所以可以用表空間號 + 頁號作為key,緩存頁作為value創建一個哈希表,在需要訪問某個頁的數據時,先從哈希表中根據表空間號 + 頁號看看有沒有對應的緩存頁,如果有,直接使用該緩存頁就好,如果沒有,那就從free鏈表中選一個空閑的緩存頁,然后把磁盤中對應的頁加載到該緩存頁的位置。
四、flush鏈表管理
如果修改了Buffer Pool中某個緩存頁的數據,那它就和磁盤上的頁不一致了,這樣的緩存頁也被稱為臟頁(Dirty Page)。
內存的數據修改后,要保證磁盤上的數據也同步進行修改,最簡單的做法就是每發生一次修改就立即同步到磁盤上對應的頁上,但是頻繁的往磁盤中寫數據會嚴重的影響程序的性能。所以每次修改緩存頁后,我們并不著急立即把修改同步到磁盤上,而是在未來的某個時間點進行同步。
在同步內存數據到磁盤中時,需要知道Buffer Pool中哪些緩存頁的數據發生了變化,所以同樣需要一個存儲臟頁的鏈表,凡是修改過的緩存頁對應的控制塊都會作為一個節點加入到一個鏈表中,因為這個鏈表節點對應的緩存頁都是需要被刷新到磁盤上的,所以也叫flush鏈表。鏈表的構造和free鏈表差不多。
五、LRU鏈表管理
Buffer Pool的大小是有限的,free鏈表總歸有用完的時候,這個時候就涉及到緩存頁淘汰的問題了,把舊的緩存頁移除,然后把新的緩存頁放進來。
Buffer Pool的初衷就是為了減少磁盤IO的次數,緩存命中率越高越好。
所以,Buffer Pool的淘汰策略使用LRU算法,淘汰最近最少使用的緩存頁,留下最近使用比較頻繁的緩存頁。
InnoDB為了知道哪些緩存頁是最近使用的,就需要再創建一個鏈表,該鏈表使用LRU算法來淘汰緩存頁,所以稱為LRU鏈表,當訪問某個頁時,它的工作過程如下:
- 如果該頁不在Buffer Pool中,就把該頁從磁盤加載到Buffer Pool中的緩存頁時,然后把該緩存頁對應的控制塊作為節點塞到LRU鏈表的頭部;
- 如果該頁已經緩存在Buffer Pool中,則直接把該頁對應的控制塊移動到LRU鏈表的頭部;
只要我們使用到某個緩存頁,就把該緩存頁調整到LRU鏈表的頭部,這樣LRU鏈表尾部就是最近最少使用的緩存頁。所以當Buffer Pool中的空閑緩存頁使用完時,到LRU鏈表的尾部找些緩存頁淘汰就可以了。
這種簡單的LRU鏈表其實是有一些問題,主要與InnoDB自生的一些特性和SQL語句有關。
mysql當然不會用這樣的LRU鏈表,它對了一下優化,詳情請見下一章。