由於硬盤和內存的造價差異,一臺主機實例的硬盤容量通常會遠超於內存容量。對於數據庫等應用而言,爲了保證更快的查詢效率,通常會將使用過的數據放在內存中進行加速讀取。
數據頁與索引頁的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 進行了優化,
- 將緩衝池分爲老生代和新生代,入緩衝池的頁,優先進入老生代,頁被訪問,才進入新生代,以解決預讀失效的問題。
- 同時採用老生代停留時間窗口機制,當數據頁被訪問且在老生代停留時間超過配置閾值的,才進入新生代,以解決批量數據訪問,大量熱數據淘汰的問題