對於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
)算法
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鏈表)
Buffer Pool的大小是有限的,因此須要將一些舊的緩存頁從Buffer Pool中移除,可是移除緩存頁時也指望緩存集中率高
爲了考慮兩種可能會致使緩存命中率下降狀況,因此須要對這個鏈表作必定的設計
預讀read ahead
- 線性預讀(使用操做系統核心提供的AIO接口異步讀取下一個區中所有的頁面到Buffer Pool)
- 隨機預讀(若是
Buffer Pool
中已經緩存了某個區的13個連續(即young區域的頭1/4)的頁面,不論這些頁面是否是順序讀取的,都會觸發一次異步
讀取本區中全部其的頁面到Buffer Pool
的請求)
這個預讀到的頁若是真的被訪問到,是能夠提升效率的,但若是沒有被訪問,就會浪費內存,且讓存放在鏈表尾部的緩存被淘汰,下降了緩存命中率;
LRU鏈表以按照最近最少使用的原則去淘汰緩存頁,將這個鏈表分爲兩截(young區和old區,以使用頻率高低區分,系統變量innodb_old_blocks_pct
肯定old區所佔比例)
當訪問某個頁不存在緩存頁中時(初始讀),將這個緩存頁的控制塊放到old區,並在對應的控制塊中記錄下訪問時間,這樣預讀/全表掃描的不被後續訪問的頁面就會逐漸從old區移除,若是後續被訪問時,比較當前訪問時間和記錄的時間是否在一個時間間隔內(系統變量innodb_old_blocks_time
查看),若是不在就會把頁放到young區域的頭部,反之,不會移動;這樣就會減小將young中使用頻率較高的頁給頂下去的機會。
可是頻繁的移動也會產生比較大的開銷,因此規定只要被訪問的緩存頁位於young區域的1/4的後面,纔會被移動到LRU鏈表的頭部,下降調整LRU鏈表的頻率