MySQL中讀頁緩沖區buffer pool詳解
Buffer pool
我們都知道我們讀取頁面是需要將其從磁盤中讀到內存中,然後等待CPU對數據進行處理。我們直到從磁盤中讀取數據到內存的過程是十分慢的,所以我們讀取的頁面需要將其緩存起來,所以MySQL有這個buffer pool對頁面進行緩存。
首先MySQL在啟動時會向操作系統申請一段連續的內存空間,這一段空間就是作為buffer pool所用。將緩存的頁放入buffer pool中管理起來。
mysql> show variables like 'innodb_buffer_pool_size'; +-------------------------+-----------+ | Variable_name | Value | +-------------------------+-----------+ | innodb_buffer_pool_size | 134217728 | +-------------------------+-----------+ 1 row in set, 1 warning (0.00 sec)
我們可以看到默認是134217728字節,即128MB。一個頁面是16KB,我們申請16KB倍數的緩存區大小就不會產生碎片。
buffer pool組成
同時呢,在buffer pool中還有包含每個頁面的控制信息,即控制塊。每個控制塊對應管理每一個頁面 (我們使用地址引用每一個頁面) ,控制塊用來存儲頁面的一些信息,控制塊的占用大小不包括在innodb_buffer_pool_size中。由MySQL在啟動時自己額外申請空間。
在控制塊和緩存頁中間會有部分碎片,就是空間無法全部利用的產生的碎片。因為MySQL向操作系統申請的內存空間需要申請一定大小的控制塊空間,不能確定具體的大小,難免回有無法利用的空間。
free鏈表
free鏈表顧名思義,就是管理空閑的緩存頁的鏈表,如果緩存頁沒有被使用,其控制塊就會連接到free鏈表上。
通過一個基節點連接控制塊形成一個free鏈表,並存儲空閑頁的數量等基本信息。
當我們從磁盤讀取一個頁到buffer pool中,就會取一個空閑的控制塊填上對應緩存頁的基本信息。
緩存頁的哈希處理
MySQL在buffer pool中怎麼快速存取一個頁,以及查看對應頁有沒有被緩存到buffer pool中呢?
這就是用到哈希表,在Java中就是hashmap,通過表空間+頁號做處理形成一個hash的key值,然後value值就是緩存頁在buffer pool中的地址。
flush鏈表的管理
學習到這一章節的時候我震驚瞭,首先確實和我的理解是不一樣的,以及到後面的MVCC確實讓我大開眼界,這是我學習一遍後回頭做的總結,所以比較言簡意賅哈。
我們使用SQL語句對某條記錄進行修改的時候,就會修改某個頁面或者多個頁面,我們對於頁面的修改呢,並不會直接對磁盤進行對應的修改,因為對於磁盤IO實在是太慢瞭,我們首先會將修改的頁面(簡稱臟頁)鏈起來,就和free鏈表差不多,就是一個基節點將對應臟頁的控制塊連接在一起。
這個flush鏈表就代表我們即將還沒有將頁面更新到磁盤的鏈表。
LRU鏈表
因為buffer pool的大小是有限的,所以我們對於緩存頁的大小是有限的,所以我們需要將不用的頁面進行一個淘汰。MySQL采用的就是LRU的方式進行淘汰。
LRU就是最久未使用淘汰的策略,我們使用一個鏈表將緩存頁面鏈起來,最近訪問的出現在最前面,最久未訪問的在鏈表末尾,當LRU滿瞭新頁面都進來機會淘汰鏈表尾部頁面。
我們直接使用LRU,當MySQL進行預讀或者全表掃描出現大量低頻頁面被讀進LRU鏈表,會導致高頻的頁面直接被淘汰掉瞭,取而代之的是一些不經常用的頁面。
預讀就是MySQL優化器認為當前請求可能會讀取的頁面,預先將其加載到內存的buffer pool中。可以分為兩種:
- 線性預讀
當讀取一個區的頁面超過系統變量innodb_read_ahead_threshold的值默認為56,也就是說當我們讀取一個區的頁面超過56頁,MySQL就會異步的讀取下一個區的所有頁面到內存中。
- 隨機預讀
如果buffer pool已經緩存瞭某個區的13個頁面,不管是不是順序的,隻要有13頁緩存瞭,就會觸發MySQL異步讀取本區的所有頁面到MySQL中。我們可以控制關閉隨機預讀,也就是系統變量innodb_random_read_ahead。默認是OFF。
所以出現瞭改進基於分區的LRU鏈表,將鏈表分為兩份。
一個是使用頻率非常高的young區域,一個是使用頻率不是很高的old區。
正常來說old區占比是37%,所以young區就占63%,我們可以通過innodb_old_blocks_pct來修改,默認就是37。
我們來講講這個基於分區的LRU鏈表。
- 首先buffer pool初始化,會將讀取的頁面直接放進old區。
- 但是如果我們對於同一個頁面的多條記錄進行訪問的話,我們就會多次訪問同一頁多次。但是如果我們是全表掃描的話,是可能會將所有頁面緩存進緩存池中的,所以MySQL對於其進行優化。
- 所以MySQL對於當頁面第一次讀入old區並在一定時間間隔(innodb_old_blocks_pct)內的多次訪問來說是不會將其放入young區進行緩存的。innodb_old_blocks_pct的值默認為1000,就是剛來的來一秒內的多次訪問是不會將其轉移到young區的。
- 如果多次訪問就會將old區的頁升級到young區。當young區的頁面被訪問,隻有young鏈表後1/4的頁面被訪問時才會將其轉置到young區鏈表頭,不然就不會改動,減少一些調整鏈表的性能損失。
刷新臟頁
MySQL會啟動後臺線程進行臟頁,也就是修改的頁面進行刷新到磁盤。
以下有兩種方式刷新臟頁:
- 從LRU的尾部掃描一些頁面,刷新其中的臟頁到磁盤中。
- 後臺線程會從LRU鏈表中old區域尾部,即不經常使用的頁面中查找有沒有臟頁,有就更新到磁盤。可以更改系統變量innodb_lru_scan_depth來控制掃描區域尾部的數量。
- 從flush鏈表中更新到磁盤。
- 我們上面說瞭flush連接這臟頁的控制塊,我們就可以將連接這flush鏈表的臟頁進行更新。
疑問:為什麼要兩種方式更新呢?我剛開始不懂這是我回過頭來看的時候就懂瞭
首先我們臟頁是緩存在buffer pool中的,但是我們buffer pool空間是有限的,又因為我們使用的是LRU的方式,又因為從flush鏈表將臟頁同步到磁盤效率實在不高,所以不會很經常去更新臟頁。如果我們不更新直接將其從LRU的鏈表拋棄也就是從緩存池中直接扔瞭,但是它是臟頁就無法同步到磁盤瞭,同時flush鏈表鏈接的也會出現問題。
所以在LRU淘汰很久未使用的頁有個前提就是它不是一個臟頁。所以我們會去檢測LRU鏈表尾部有沒有臟頁,然後更新它,我們才能去淘汰掉這些頁。
flush鏈表更新那就是它的本職工作瞭,它存這個也是幹這個的,應該沒有什麼問題。
當系統十分繁忙,buffer pool使用量不足的時候,因為磁盤IO太慢瞭,所以會出現一種情況,就是大量的用戶線程也在進行這個同步臟頁的活。不同步臟頁然後淘汰buffer pool的頁面,沒法讀取頁面啊。
多個buffer pool實例
我們可以設置多個buffer pool來實現多實例提高性能。
mysql> show variables like 'innodb_buffer_pool_instances'; +------------------------------+-------+ | Variable_name | Value | +------------------------------+-------+ | innodb_buffer_pool_instances | 1 | +------------------------------+-------+ 1 row in set, 1 warning (0.00 sec)
我們可以設置innodb_buffer_pool_instances系統變量來控制實例變量。
但是當buffer pool的大小小於1G的時候,設置2個實例也是沒有用的(會被恢復成1個),多實例的情況是建立在大內存的情況下的。
動態調整buffer pool大小
在MySQL5.7.5後,MySQL中的buffer pool的大小是以chunk來分配瞭,如下圖。
一個buffer pool是由多個chunk組成的,所以MySQL向操作系統申請連續的內存空間,就是以chunk的方式來申請的,這樣我們可以在MySQL運行時調整buffer pool的大小。但是chunk的大小是不能在運行時更改的,這樣是很耗費性能的。?
innodb_buffer_pool_size / innodb_buffer_pool_instances = 每個實例buffer pool的大小。
每個實例的大小 / innodb_buffer_pool_chunk_size = 每個實例由多少個chunk構成。
不是弄很明白,怎麼動態調整大小,我調整瞭但是mysqld占用內存大小還是隻能重啟才能生效,我不會。
查看buffer pool具體的信息
show engine innodb status;
到此這篇關於MySQL中讀頁緩沖區buffer pool的文章就介紹到這瞭,更多相關MySQL buffer pool內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!
推薦閱讀:
- MySQL之Innodb_buffer_pool_size設置方式
- MySQL系列之七 MySQL存儲引擎
- 詳解分析MySQL8.0的內存消耗
- MySQL查看數據庫狀態命令詳細講解
- mysql事務和隔離級別底層原理淺析