InnoDB緩存---InnoDB Buffer Pool

InnoDB Buffer Pool

定義

對於InnoDB存儲引擎,無論用戶數據仍是系統數據都是以頁的形式存儲在表空間進行管理的,其實都是存儲在磁盤上的。git

當InnoDB處理客戶端請求,須要讀取某頁的一條記錄時,就會將這個頁中的全部數據加載到內存中,再進行讀寫操做,當讀寫操做完成後,不是先將內存空間釋放,而是將其緩存起來,當下次有一樣的請求時,能夠省去磁盤IO的開銷。github

而InnoDB緩存這些頁的內存就叫作Buffer Pool,(5.7.5v以前這是一塊連續的內存,能夠在配置文件中以innodb_buffer_pool_size動態修改其大小,這以後,以chunk爲單位想操做系統申請空間,Buffer Pool由若干個chunk組成,一個chunk就是一片連續的空間)若是Buffer Pool大小大於1G時,那麼能夠被拆分紅若干個小的獨立的實例(系統變量innodb_buffer_pool_instances設置個數),緩存頁的映射是有特定算法的,因此不存在重複緩存頁,且在多線程訪問時,互不影響。web

(TODO Buffer Pool配置注意事項+Buffer Pool的狀態信息查看SHOW ENGINE INNODB STATUS算法

image_1d15r7te41q58egj1b4plh615ug7r.png-125.5kB

組成

Buffer Pool由n個控制塊和n個緩存頁還有一些碎片組成,控制塊與緩存頁一一對應。緩存

  • 緩存頁:和磁盤上的頁同樣都是16kb大小,存放在Buffer Pool後邊服務器

  • 控制塊:存放了一些控制信息包含頁所屬的表空間編號/頁號/緩存頁在Buffer Pool中的位置/鏈表節點信息/鎖信息/LSN信息等,存放在Buffer Pool前邊,大小約爲緩存頁的5%多線程

  • 碎片:Buffer Pool中不能再分配的空間異步

管理

Buffer Pool在MySQL服務器啓動時,就會完成初始化工做:申請Buffer Pool內存空間,劃分若干對控制塊和緩存頁;其中緩存頁的的使用狀況會用到一些數據格式來管理,如空閒緩存頁,被修改過的髒頁等用到雙向鏈表。操作系統

空閒頁

free鏈表記錄Buffer Pool中哪些緩存頁是可用的,將緩存頁對應的控制塊做爲一個節點存放在鏈表中,在Buffer Pool初始化時,是將全部控制塊都存放的。free鏈表定義了一個基節點,存儲了鏈表的頭尾節點;每當須要使用一個緩存頁時,都會從free鏈表取出一個空閒的緩存頁,將控制信息填上,並將對應的緩存頁節點從free鏈表移除。線程

緩存頁查找

申明一個hash表,以表空間號+頁號爲key,緩存頁爲value存儲已經被加載到緩存中的緩存頁,這樣就能夠很快定位哪些頁已經被緩存了,就無需重複爲這個頁申請緩存頁

髒頁

髒頁是修改了已經被加載到Buffer Pool中的緩存頁數據,致使它和磁盤上的不一致。爲了將這些髒頁都同步到磁盤上,建立了一個flush鏈表,用於存儲這些髒頁的信息,隔一段時間就將這些數據同步到磁盤。flush鏈表和free鏈表構造同樣

刷新髒頁到磁盤,有兩種方式(在flush鏈表的頁確定也在LRU鏈表)

  • 從LRU鏈表的冷數據中刷新一部分頁面到磁盤BUF_FLUSH_LRU:後臺線程定時從LRU鏈表尾部開始掃描,若是發現髒頁就會刷新到磁盤
  • flush鏈表中刷新一部分頁面到磁盤BUF_FLUSH_LIST:後臺線程定時從flush鏈表中刷新一部分頁面到磁盤
  • 刷新LRU鏈表尾部單頁到磁盤BUF_FLUSH_SINGLE_PAGE:當用戶線程準備加載的一個磁盤頁到Buffer Pool,卻沒有空間,先查找尾部是否有能夠直接釋放卻沒有修改的緩存頁,若是沒有就會強制將LRU鏈表尾部的一個髒頁同步到磁盤

緩存淘汰

Buffer Pool的大小是有限的,因此須要將一些舊的緩存頁從Buffer Pool中移除,可是移除緩存頁時也指望緩存集中率高

問題

爲了考慮兩種可能會致使緩存命中率下降狀況,因此須要對這個鏈表作必定的設計

  1. InnoDB提供「預讀」的功能,即在執行某個請求後,將它認爲以後可能會讀到的一些頁面預先加載到Buffer Pool中,並存放到LRU鏈表的頭部

預讀read ahead

  • 線性預讀(使用操做系統核心提供的AIO接口異步讀取下一個區中所有的頁面到Buffer Pool)
  • 隨機預讀(若是Buffer Pool中已經緩存了某個區的13個連續(即young區域的頭1/4)的頁面,不論這些頁面是否是順序讀取的,都會觸發一次異步讀取本區中全部其的頁面到Buffer Pool的請求)

這個預讀到的頁若是真的被訪問到,是能夠提升效率的,但若是沒有被訪問,就會浪費內存,且讓存放在鏈表尾部的緩存被淘汰,下降了緩存命中率;

  1. 全表掃描時讀取表中全部記錄,這時會將該表的全部頁都存放到Buffer Pool中,這樣可能會讓Buffer Pool被徹底覆蓋,一些命中率很是高的緩存頁就被淘汰了,下降了緩存命中率;
實現

LRU鏈表以按照最近最少使用的原則去淘汰緩存頁,將這個鏈表分爲兩截(young區和old區,以使用頻率高低區分,系統變量innodb_old_blocks_pct肯定old區所佔比例)

當訪問某個頁不存在緩存頁中時(初始讀),將這個緩存頁的控制塊放到old區,並在對應的控制塊中記錄下訪問時間,這樣預讀/全表掃描的不被後續訪問的頁面就會逐漸從old區移除,若是後續被訪問時,比較當前訪問時間和記錄的時間是否在一個時間間隔內(系統變量innodb_old_blocks_time查看),若是不在就會把頁放到young區域的頭部,反之,不會移動;這樣就會減小將young中使用頻率較高的頁給頂下去的機會。

可是頻繁的移動也會產生比較大的開銷,因此規定只要被訪問的緩存頁位於young區域的1/4的後面,纔會被移動到LRU鏈表的頭部,下降調整LRU鏈表的頻率

相關文章
相關標籤/搜索