一文讀懂 InnoDB 緩衝池(buffer pool) 工做原理

緩衝池的用處

對於使用 InnoDB 做爲存儲引擎的表來講,不論是用於存儲用戶數據的索引,仍是各類系統數據,都是以頁的形式存放在表空間中的,而所謂的表空間只是 InnoDB 對文件系統上一個或幾個實際文件的抽象,也就實際數聽說到底仍是存儲在磁盤上的。算法

磁盤的速度很慢,怎麼能配得上「快如閃電」的CPU 呢?緩存

InnoDB 存儲引擎在處理客戶端的請求時,當須要訪問某個頁的數據時,就會把完整的頁的數據所有加載到內存中。服務器

也就是說即便咱們只須要訪問一個頁的一條記錄,那也須要先把整個頁的數據加載到內存中。性能

緩衝池內部組成

緩衝池中默認的緩存頁大小和在磁盤上默認的頁大小是同樣的,通常是16KB。優化

爲了更好的管理這些在緩衝池中的緩存頁,InnoDB爲每個緩存頁都建立了一些所謂的控制信息。spa

這些控制信息包括該頁所屬的表空間編號、頁號、緩存頁在緩衝池中的地址、鏈表節點信息、一些鎖信息。操作系統

緩衝池的一些參數:
SHOW VARIABLES LIKE 'innodb_buffer_pool%';
free 鏈表
當最初啓動MySQL服務器的時候,此時並無真實的磁盤頁被緩存到緩衝池中,以後隨着程序的運行,會不斷的有磁盤上的頁被緩存到緩衝池中。3d

從磁盤上讀取一個頁到緩衝池中的時候該放到哪一個緩存頁的位置呢?code

思路:區分緩衝池中哪些緩存頁是空閒的,哪些已經被使用了。blog

把全部空閒的緩存頁對應的控制塊做爲節點放到一個鏈表中,這個鏈表叫做 free 鏈表。

flush 鏈表

若是咱們修改了緩衝池中某個緩存頁的數據,那它就和磁盤上的頁不一致了,這樣的緩存頁也被稱爲髒頁(dirty page)。

最簡單的作法就是每發生一次修改就當即同步到磁盤上對應的頁上,可是頻繁的往磁盤中寫數據會嚴重的影響程序的性能。

因此,Innodb 建立了一個存儲髒頁的鏈表,凡是修改過的緩存頁對應的控制塊都會做爲一個節點加入到一個鏈表中,在將來的某個時間點進行同步。這個鏈表叫作 flush 鏈表。

緩存不夠的窘境

緩衝池對應的內存大小畢竟是有限的,若是須要緩存的頁佔用的內存大小超過了緩衝池大小,也就是已經沒有多餘的空閒緩存頁的時候怎麼辦?

把某些舊的緩存頁從緩衝池中移除,而後再把新的頁放進來。

移除哪些緩存頁?這就須要引入緩存淘汰機制了。

緩存淘汰機制

緩存淘汰有如下兩個目的:

  • 實現淘汰
  • 使緩存命中率高

緩存淘汰機制比較經常使用的是用 LRU (Least recently used)算法。

傳統LRU

LRU 的兩種狀況:

(1)頁已經在緩衝池裏,那就只作「移至」LRU頭部的動做,而沒有頁被淘汰;

(2)頁不在緩衝池裏,除了作「放入」LRU頭部的動做,還要作「淘汰」LRU尾部頁的動做;

在 InnoDB 中,傳統的 LRU 會遇到兩個問題:

(1)預讀失效;

(2)緩衝池污染;

什麼是預讀失效?

因爲預讀 (Read-Ahead),提早把頁放入了緩衝池,但最終 MySQL 並無從頁中讀取數據,稱爲預讀失效。

如何對預讀失效進行優化?

要優化預讀失效,思路是:

(1)讓預讀失敗的頁,停留在緩衝池 LRU 裏的時間儘量短;

(2)讓真正被讀取的頁,才挪到緩衝池 LRU 的頭部;

以保證,真正被讀取的熱數據留在緩衝池裏的時間儘量長。

具體方法是:

(1)將LRU分爲兩個部分:

  • new 區(new sublist)
  • old 區(old sublist)

(2)兩個區首尾相連,即:new 區的尾(tail)鏈接着 old 區的頭(head);

(3)新頁(例如被預讀的頁)加入緩衝池時,只加入到 old 區頭部:
若是數據真正被讀取(預讀成功),纔會加入到 new 區的頭部
若是數據沒有被讀取,則會比 new 區裏的「熱數據頁」更早被淘汰出緩衝池

改進版緩衝池LRU可以很好的解決「預讀失敗」的問題。

查看系統變量 innodb_old_blocks_pct 的值來肯定old區域在LRU鏈表中所佔的比例
SHOW VARIABLES LIKE 'innodb_old_blocks_pct';

什麼是 MySQL 緩衝池污染?
當某一個SQL語句,要批量掃描大量數據時,可能致使把緩衝池的全部頁都替換出去,致使大量熱數據被換出,MySQL性能急劇降低,這種狀況叫緩衝池污染。

例如,有一個數據量較大的用戶表,當執行
select * from user where name like "%test%";

要優化緩衝池污染,思路是:

(1)不讓批量掃描的大量數據進入到 new 區;

(2)讓真正被讀取的頁,才挪到緩衝池 LRU 的頭部;

具體實現:
加入了一個「old 區停留時間」的機制:
在 old 區域的緩存頁進行第一次訪問時就在它對應的控制塊中記錄下來這個訪問時間,若是後續再次訪問的時間與第一次訪問的時間在某個時間間隔內(即該緩存頁在 old 區的存在時間在某個時間間隔內),那麼該頁面就不會被從old 區移動到 new 區的頭部。

上述的全表掃描執行:

(1) 掃描過程當中,須要新插入的數據頁,都被放到old區

(2) 一個數據頁會有多條記錄,所以一個數據頁會被訪問屢次

(3) 因爲是順序掃描,數據頁的第一次被訪問和最後一次被訪問的時間間隔不會超過1S,所以仍是會留在old區

(4) 繼續掃描,以前的數據頁不再會被訪問到,所以也不會被移到 new 區,最終很快被淘汰

這個間隔時間是由系統變量 innodb_old_blocks_time 控制的。

SHOW VARIABLES LIKE 'innodb_old_blocks_time';

配置緩衝池時的注意事項
innodb_buffer_pool_size
innodb_buffer_pool_chunk_size × innodb_buffer_pool_instances 的倍數(這主要是想保證每個 緩衝池 實例中包含的 chunk 數量相同)。

查看Buffer Pool的狀態信息

SHOW ENGINE INNODB STATUS\G

一些參數以下:

Total memory allocated :表明 Buffer Pool 向操做系統申請的連續內存空間大小,包括所有控制塊、緩存頁、以及碎片的大小。

Buffer pool size:表明該 Buffer Pool 能夠容納多少緩存頁,單位是頁

Free buffers:表明當前 Buffer Pool 還有多少空閒緩存頁,也就是 free 鏈表中還有多少個節點。

Database pages:表明 LRU 鏈表中的頁的數量,包含 new 和 old 兩個區域的節點數量。

Old database pages:表明 LRU 鏈表 old 區域的節點數量。

Modified db pages:表明髒頁數量,也就是 flush 鏈表中節點的數量。

總結

一、磁盤太慢,用內存做爲緩存頗有必要。

二、緩衝池本質上是InnoDB向操做系統申請的一段連續的內存空間,能夠經過innodb_buffer_pool_size 來調整它的大小。

三、InnoDB 使用了許多鏈表來管理緩衝池。

四、緩衝池的常見管理算法是 LRU

五、InnoDB 對普通 LRU 進行了優化:分爲 new 區和 old 區,加入「停留時間」機制。

更多好文,關注公衆號獲取

file

相關文章
相關標籤/搜索