在上一篇文章中,介紹了LRU算法在Redis之中的應用,本篇繼續給各位道友介紹在Mysql的InnobDB引擎中,是如何使用LRU算法的。html
首先來介紹下InnoDB的緩衝池,緩衝池簡單來講就是一塊內存區域,該區域內緩存着InnoDB訪問存儲在磁盤的數據和索引信息。緩衝池有兩個做用,一是提升了大容量讀取操做的效率,二是提升了緩存管理的效率。調配緩存池參數,使得常常訪問的參數可以保留在緩存池中是一個很重要的Mysql優化手段。mysql
一個InnoDB緩存池的內存結構圖以下圖所示:算法
圖源自《Mysql技術內幕:InnoDB存儲引擎》sql
咱們能夠經過SHOW ENGINE INNODB STATUS命令來查看緩存池在InnoDB引擎中的表現:shell
----------------------
BUFFER POOL AND MEMORY
----------------------
Total memory allocated 6593445888; // 爲緩衝池分配的總內存(字節)
Dictionary memory allocated 7687783 // 爲InnoDB數據字典分配的總內存(字節)
Buffer pool size 393208 // 分配給緩衝池的頁面總大小(頁)
Free buffers 352642 // 緩衝池空閒列表的頁面總大小(頁)
Database pages 40485 // 緩衝池LRU列表的頁面總大小。(頁)
Old database pages 14967 // 緩衝池舊LRU子列表的頁面總大小(頁)
Modified db pages 4 // 緩衝池中當前修改的頁面數。
Pending reads 0 // 等待讀入緩衝池的緩衝池頁面數。
Pending writes: LRU 0, flush list 0, single page 0 // 從LRU列表的底部開始寫入的緩衝池中的舊髒頁數。 // 檢查點期間要刷新的緩衝池頁面數。
// 緩衝池中暫掛的獨立頁面寫入數。
Pages made young 5, not young 0 // 緩衝池LRU列表中變年輕的頁面總數
// 緩衝池LRU列表中未設置爲年輕的頁面總數
...
複製代碼
完整的緩存池狀態信息能夠在這裏找到:緩存池狀態信息數據庫
爲了不多個線程讀寫緩存池引發的併發衝突,InnoDB能夠配置多個緩存池,由參數innodb_buffer_pool_instances
指定,內部使用散列表進行分配和管理。緩存
一般來講,當緩存池的大小越大,則Mysql表現的越像一個內存數據庫。咱們能夠在啓動時或者運行時經過innodb_buffer_pool_size
參數動態地調整緩存池的大小,須要注意的innodb_buffer_pool_size
的大小會自動的調整爲InnoDB緩存池塊innodb-buffer-pool-chunk-size
(默認爲128M)的整倍數。併發
爲避免潛在的性能問題,緩存池大小/緩存池塊大小(
innodb_buffer_pool_size
/innodb_buffer_pool_chunk_size
)的數量不該超過1000。dom
說到緩存,必須有緩存刷新機制,即剔除緩存中的髒頁(已經被修改,可是並未刷入磁盤中的數據頁)。異步
在5.7以上的版本中,InnoDB會啓動默認四個線程併發的來執行緩存池中髒頁的清除。髒頁的清除有兩種模式:
innodb_max_dirty_pages_pct_lwm
(低水平線默認爲25%)時,啓動普通模式將髒頁刷新到磁盤中。innodb_max_dirty_pages_pct
(默認爲75%)時,啓動更快的刷新模式,儘快的將髒頁刷新到磁盤當中。InnoDB的緩存池不只是被動地緩存,並且會異步地預先從磁盤中讀取數據頁,有兩種方式:
線性:根據緩存池的訪問數據的順序來預讀,當讀取某一區(Extend)中的頁(Page)的數據超過innodb_read_ahead_threshold
時,則將該區中剩餘的全部頁都加載到緩存池中。
隨機:根據緩存池中的已有頁面進行預讀,而無論他們的順序,當發現緩存池中某一區內頁的數量超過了innodb_random_read_ahead
,則將改區中剩餘的全部頁都加載到緩存池中。
在瞭解了InnoDB的緩存池概念後,咱們來看看背後支持緩存池工做的算法。
當咱們使用樸素的LRU算法時,會發現若是有批量的操做時,會打亂緩存數據,大大下降了緩存命中率。而在Mysql當中會有大量的預讀及全表掃描的操做,爲了使得真真的熱數據留在內存中,InnoDB緩存池採用了一種變種的LRU算法,有些像我在這篇文章中寫到的LRU-K算法。
新進入緩存池的頁並不會直接進入LRU鏈表的頭部,而是插入到距離鏈表尾3/8的位置(能夠由innodb_old_blocks_pct參數進行配置),咱們將距離鏈表尾3/8以上的位置稱爲新子列表
,如下的位置稱爲舊子列表
,數據在鏈表中自底而上稱爲變年輕
,反之稱爲變老
。下圖是一個示意圖:
變年輕
變年輕分爲兩種狀況,第一種是來源於用戶的操做而須要讀取頁面,此時會直接使該頁直接移至新子列表鏈表頭部。第二種是來源於數據庫內部的預讀操做,則在距離插入innodb_old_blocks_time(默認爲1000ms)的時間內,即便訪問了該頁,該頁也不會別移到LRU鏈表的頭部。
也就是說,若是是來源於用戶的操做,則最起碼須要兩次操做才能變年輕。而若是是預讀操做,則須要加上一個等待期限。
變老
隨着鏈表數據的替換和訪問,整個列表中的數據會天然的變老。最終最老的頁面會從尾部逐出。
本文介紹了Mysql的InnoDB引擎的緩存池的概念,及其對於LRU算法的改造。介紹了另外一種解決LRU列表被污染的解決方案。