由於硬碟和記憶體的造價差異,一臺主機例項的硬碟容量通常會遠超於記憶體容量。對於資料庫等應用而言,為了保證更快的查詢效率,通常會將使用過的資料放在記憶體中進行加速讀取。
資料頁與索引頁的LRU
資料頁和索引頁的目的在於快取一部分的表資料和索引資料,其資料總量通常會超過緩衝池大小,所以緩衝池中應只緩衝那些經常使用的熱點資料。InnoDB記憶體管理使用的是最近最少使用(Least Recently Used, LRU)演算法。來淘汰最久未使用的資料
在一般的LRU演算法中,當連結串列中的某一個資料被讀取時,將會將其放置於隊首。當新增資料且連結串列已達最大數量時,將連結串列尾部的資料移除,並將新增的資料置於連結串列首部。
InnoDB的LRU並沒有使用傳統的雙端連結串列,而是做了改進,這裡有兩個問題:
- 預讀失效
- 緩衝池汙染
最佳化預讀失效
由於預讀(Read-Ahead),提前把頁放入了緩衝池,但最終 MySQL 並沒有從頁中讀取資料,稱為預讀失效。
Read-Ahead機制
Read-Ahead用於非同步預取buffer pool中的多個page的一個預測行為。
InnoDB使用兩種提前預讀Read-Ahead演算法來提高I/O效能。
-
Linear read-ahead 線性預讀
如果一個extent中的被順序讀取的page超過或者等於 innodb_read_ahead_threshold 引數變數時,Innodb將會非同步的將下一個extent讀取到buffer pool中,innodb_read_ahead_threshold可以設定為0-64的任何值(注:innodb中每個extent就只有64個page),預設為56。值越大,訪問模式檢查就越嚴格。
-
Random read-ahead 隨機預讀
如果當同一個extent中連續的13個page在buffer pool中發現時,Innodb會將該extent中的剩餘page讀到buffer pool中。控制引數 innodb_random_read_ahead 預設沒有開啟。
要最佳化預讀失效,思路是:
- 讓預讀失敗的頁,停留在緩衝池LRU裡的時間儘可能短
- 讓真正被讀取的頁,才挪到緩衝池LRU的頭部
InnoDB 的具體解決方法
由上圖可以看出 InnoDB 將 LRU List 分為兩部分,預設前 5/8 為 New Sublist(新生代)用於儲存經常被使用的熱點資料頁,後 3/8 為 Old Sublist(老生代),新讀入的資料頁預設被放到 Old Sublist 中,只有滿足一定條件後,才會被移入 New Sublist。
新生代和老生代代比例在 MySQL 中透過引數 innodb_old_blocks_pct 控制,值的範圍是5到95.預設值是37(即池的3/8)。
- 如果資料頁真正被讀取(預讀成功),才會加入到新生代的頭部
- 如果資料頁沒有被讀取,則會比新生代裡的“熱資料頁”更早被淘汰出緩衝池
舉個例子,整個緩衝池如圖
假如有一個頁號為 50 的資料頁頁被預讀加入緩衝池:
(a). 頁號為50 的資料頁只會從老生代頭部插入,老生代尾部(也是整體尾部)的頁會被淘汰掉,即 8 號資料頁被淘汰。
(b). 假如頁號為50 的資料頁不被真正讀取,即預讀失敗,它將比新生代的資料更早淘汰出緩衝池
(c). 假如 50 這一頁立刻被讀取到,例如SQL訪問了頁內的行row資料。它會被立刻加入到新生代的頭部,同時新生代的頁會被擠到老生代,此時並不會有頁面被真正淘汰
改進版緩衝池LRU能夠很好的解決“預讀失敗”的問題。但仍然無法解決緩衝池被汙染但問題。
緩衝池汙染
當某一個SQL語句,要批次掃描大量資料時,可能導致把緩衝池的所有頁都替換出去,導致大量熱資料被換出,MySQL 效能急劇下降,這種情況叫緩衝池汙染。
解決方法
緩衝池加入了一個“老生代停留時間視窗”的機制:
(a). 假設T=老生代停留時間視窗
(b). 插入老生代頭部的頁,即使立刻被訪問,並不會立刻放入新生代頭部
(c). 只有滿足“被訪問”並且“在老生代停留時間”大於T,才會被放入新生代頭部
假如批次資料掃描,有91、92、93、94、95、96、97、98、99等頁面將要依次被訪問
如果沒有“老生代停留時間視窗”的策略,這些批次被訪問的頁面,會置換出大量熱資料。
加入“老生代停留時間視窗”策略後,短時間內被大量載入的頁,並不會立刻插入新生代頭部,而是優先淘汰那些,短期內僅僅訪問了一次的頁。
只有在老生代呆的時間足夠久,停留時間大於T,才會被插入新生代頭部。
老生代的停留時間由引數 innodb_old_blocks_time
控制,單位為毫秒,預設是1000
總結
- 緩衝池(buffer pool)是一種常見的降低磁碟訪問的機制
- InnoDB的緩衝池以資料頁(page)為單位快取資料
- InnoDB 對普通 LRU 進行了最佳化,
- 將緩衝池分為老生代和新生代,入緩衝池的頁,優先進入老生代,頁被訪問,才進入新生代,以解決預讀失效的問題。
- 同時採用老生代停留時間視窗機制,當資料頁被訪問且在老生代停留時間超過配置閾值的,才進入新生代,以解決批次資料訪問,大量熱資料淘汰的問題