本文主要說明 InnoDB Buffer Pool 的內部執行原理,其生效的前提是使用到了索引,若是沒有用到索引會進行全表掃描。html
在 InnoDB 存儲引擎層維護着一個緩衝池,經過其能夠避免對磁盤頻繁的IO操做。下面是其內部結構的概要圖(實際沒有這麼簡單,本文只着重說一下它的「讀」、「寫」緩存)。其本質就是將磁盤上的數據頁移到內存中,以此來減小對磁盤數據的直接IO。mysql
能夠看到內部含有一個小區域,叫作 Change Buffer,這個是用 InnoDB 的 "寫"緩存,而外面的是 InnoDB 的 「讀」緩存。算法
MySQL 內部通常都會使用緩衝池,而若是屢次語句操做的是相鄰的記錄,那麼就會屢次進行磁盤讀取,致使速度下降,因此 MySQL 通常在讀取數據時都是採用預讀方式,讀取指定數據周圍的多條數據。而在 InnoDB 引擎中的數據是以頁爲單位進行存儲的,而且提出了「數據頁」概念。數據頁的結構以下,大小默認爲 16K,關於數據頁這裏就不過多闡述,感興趣能夠查看原博客。對硬盤上的數據讀取最小單位就是數據頁。sql
而在數據頁上面,還分爲區(Extent)、段(Segment)、表空間(Tablespace),它們之間的包含關係以下圖。具體能夠查看原博客。數據庫
InnoDB 引擎在預讀時, 有兩種預讀算法。線性預讀和隨機預讀。緩存
選擇是否預讀下一個 Extent 的數據。有一個重要的參數 innodb_read_ahead_threshold,若是當前 Extent 中連續讀取的數據頁超過規定值,就會將下一個 Extent 的數據也讀到緩衝池中。innodb_read_ahead_threshold 的範圍是 0-64(由於一個 Extent 也就64頁)。併發
用來設置是否將當前 Extent 的剩餘頁也預讀到緩衝池中,因爲這種預讀性能不穩定,因此MySQL 5.5開始默認關閉。dom
InnoDB 的緩衝池數據的存儲算法是改進版的 LRU 算法,以此來避免了傳統 LRU 算法的兩個問題,預讀失效和緩衝池污染。高併發
LRU 算法簡單來講,若是用鏈表來實現,將最近命中(加載)的數據頁移在頭部,未使用的向後偏移,直至移除鏈表。這樣的淘汰算法就叫作 LRU 算法。可是其會含有前面說得兩個問題。性能
在磁盤上讀取數據時,可能會由於操做不當致使多個用不到的數據頁加載到緩衝池。從而致使以前常常被使用的數據頁緩存被無用的數據頁擠到尾部,甚至被移出緩存,那麼就會下降性能。而 InnoDB 的解決方案是將緩衝池分爲兩部分,新生代和老年代,比例默認爲5:3,分別存儲經常使用的數據頁以及不經常使用的數據頁,新生代位於頭部,新生代位於尾部,這兩部分都有頭部和尾部。當從磁盤的數據頁移入緩衝池中時,首先是放入老年代的頭部,而後進行篩選,使用到的數據頁會移入新生代的頭部,未使用的數據頁會隨着時間流逝而慢慢移入老年代的尾部,直至淘汰。
在處理數據頁時,若是須要對大量數據頁進行篩選(可是沒有用到),那麼仍是會使大量的熱點數據頁被擠出。如 select * from student where name like '張%';name字段包含索引,那麼在執行時雖然會先加載到老年代的頭部,可是由於每條數據都須要篩選,因此都會移入新生代頭部,致使新生代熱點數據頁被擠到老年代甚至移除。InnoDB 爲了解決這個問題,使用了 "老年代停留時間窗口" 機制,這個機制是設置一個時間,若是在老年代的數據頁被調用後還須要去檢查它在老年代的停留時間是否達到了這個規定時間,達到了才能移入新生代頭部,不然只會移到老年代頭部。
寫緩存(Change Buffer)在5.5以前叫作 插入緩存(insert Buffer),由於只支持插入的緩存,在隨後版本又添加了 update、delete,因此更名 change Buffer。由於直接對磁盤進行IO操做會比較耗時,若是咱們的程序在高併發的場景,同時某段時間寫操做很是多,那麼若是直接更新到磁盤上數據庫的壓力就會很是大,甚至崩潰。爲了不這種狀況,能夠錯開高峯期,讓數據在系統空閒時再更新到磁盤,那麼該如何實現,Change Buffer就起到這樣的做用。
在更新語句進來時,首先會判斷數據頁緩存中有沒有對應的數據,若是有直接更新對應的緩存數據,不然將其記錄在 Change Buffer 中。隨後(無論前面是哪一種狀況都會執行)再將這條sql依次寫入 redo log、bin log(Server 層的日誌,全部執行引擎均可以用,而 redo log 是InnoDB內部維護的,bin log 通常用於主從複製)。
將日誌中的sql更新到硬盤上的操做叫作「落盤(merge)」。
一、mysql系統後臺會按期落盤
二、查詢 redo log中sql操做過的數據時須要先落盤
三、mysql 正常關閉時
一、更新後馬上須要讀取該數據場景少。由於讀取更新過的數據須要先落盤,那麼 Change Buffer 存在的意義就沒有了,同時還增長了redo log 寫入的成本。
二、非惟一索引,若是使用的是惟一索引進行查詢,那麼操做的數據須要進行惟一性檢查,因此須要將相應數據頁先加載到緩衝池中,而後再判斷,更新,過程當中不會用到 Change Buffer。
寫入redo log不也是磁盤數據IO麼?爲何就比直接更新到磁盤上效率高?
使用 redo log 只是將操做存儲進去,而更新到磁盤數據則是須要先讀操做查找 B+ 樹,找到數據後再進行寫操做。
Buffer Pool :
一、innodb_buffer_pool_size:緩衝池大小,在內存足夠的條件下,越大越好。
二、innodb_old_blocks_pct:老年代佔整個LRU鏈長度的比例,默認是37,即整個LRU的新生代和老年代長度比例是63:37。(若是配置是100就變成普通的LRU了)
三、innodb_old_blocks_time:老年代停留時間窗口,單位是毫秒,默認是1000,即同時知足「被訪問」與「在老年代停留時間超過1秒」兩個條件,纔會被插入到新生代頭部。
Change Buffer:
一、innodb_change_buffer_max_size:
配置寫緩衝的大小,佔整個緩衝池的比例,默認值是25%,最大值是50%。
畫外音:寫多讀少的業務,才須要調大這個值,讀多寫少的業務,25%其實也多了。
二、innodb_change_buffering:
介紹:配置哪些寫操做啓用寫緩衝,能夠設置成all/none/inserts/deletes等。
參考博客:
https://blog.csdn.net/mashaokang1314/article/details/109716569
https://blog.csdn.net/fu_zhongyuan/article/details/90244503