mysql Innodb引擎中緩存池 Buffer Pool原理總結(jié)

緩存

????mysql Innodb引擎在處理客戶端請求時,當(dāng)訪問某個頁的數(shù)據(jù)的時候,即使我們請求的是某個頁中的一條數(shù)據(jù)記錄,也會把整個頁從磁盤加載到內(nèi)存中, 然后在內(nèi)存中就可以對數(shù)據(jù)進行讀寫了,? 數(shù)據(jù)讀寫之后并不著急把數(shù)據(jù)對應(yīng)的內(nèi)存釋放掉,而是將其緩存起來,等再次有數(shù)據(jù)請求的時候,省去了訪問磁盤的IO操作了。

緩沖池 Buffer Pool

? ? Innodb設(shè)計緩沖池,在mysql啟動時申請一大塊內(nèi)存,內(nèi)存的名字就叫做Buffer Pool,其大小是可以通過參數(shù) innodb_buffer_pool_size進行配置的 。

Buffer Pool內(nèi)部組成? ??

Buffer Pool 緩存頁的大小和磁盤上緩存頁的大小是一致的,都是16kb,為了更好地管理這些緩存頁,需要為每一個緩存頁創(chuàng)建一個控制信息塊,內(nèi)容包括緩存頁相關(guān)的一些信息:頁所屬的表空間編號,頁號,頁在緩沖池中的地址等一些數(shù)據(jù);每個控制信息塊所占有的內(nèi)存是固定的,控制信息塊和緩存頁是一一對應(yīng)的,在緩存中,控制塊在Buffer Pool前部分,緩存頁在Buffer Pool后部分。

????mysql服務(wù)器系統(tǒng)啟動后,首先申請Buffer Pool內(nèi)存,然后將內(nèi)存劃分為若干個控制信息塊和內(nèi)存塊,此時每個塊尚未進行緩存數(shù)據(jù),此時需要進行管理這些塊,把所有的空閑的塊通過鏈表關(guān)聯(lián)到一起,這個鏈表稱為 free鏈表(和STL中 內(nèi)存池申請內(nèi)存后使用鏈表管理方式類似),每一個緩存頁對應(yīng)的控制信息塊都加入了這個鏈表中。

? ? 鏈表進行管理這些空閑塊,那么關(guān)于鏈表的信息也需要進行存儲,Innodb中,特意定義了一個稱為 基節(jié)點? ? ,包含鏈表的頭和尾以及節(jié)點個數(shù)信息?;?jié)點所占有的內(nèi)存不屬于Buffer Pool中的,而是單獨申請的一塊內(nèi)存,占用40字節(jié)的內(nèi)存。

? ? 有了free鏈表之后,當(dāng)磁盤中讀取數(shù)據(jù)后,從free鏈表中獲取一個空閑的塊緩存數(shù)據(jù),并將此塊從free鏈表中移除,表示緩存已經(jīng)被使用(讀STL內(nèi)存分配也是如此操作)。

緩存頁的hash處理(查找)

????請求數(shù)據(jù)時,如果數(shù)據(jù)在緩存池中,那么直接從緩存池中獲取,怎么在緩存池中進行查找呢?? 一個個遍歷效率閑的太慢了,根據(jù)key進行查找快速的方式我們很容易想到hash,解決方案是? 采用表空間號+頁號作為key,緩存頁作為value創(chuàng)建一個hash表;訪問某個緩存頁數(shù)據(jù)時,先查找表空間號+頁號,如果有則獲取緩存頁,如果沒有則從磁盤讀取后獲取一個空閑塊進行緩存。

flush鏈表的管理(更新)

????如果修改了某個緩存頁的數(shù)據(jù),那么就會和磁盤上數(shù)據(jù)保持不一致,此時這個緩存頁被稱為臟頁(dirty page), 最簡單的方式是如果出現(xiàn)臟頁,那么立即同步到磁盤,頻繁往寫數(shù)據(jù)會嚴(yán)重影響程序的性能,so每次出現(xiàn)臟頁,并不著急立即同步到磁盤,在后面某個節(jié)點進行同步(后詳細(xì)敘述);對于在緩存中出現(xiàn)的N多個臟頁也需要我們進行管理,不然后面進行同步的時候找不到該同步哪些頁,每次出現(xiàn)數(shù)據(jù)更新,成為臟頁后這個頁會被添加到一個flush鏈表中,flush鏈表構(gòu)造和free鏈表差不多,也有個基節(jié)點保存鏈表信息:頭節(jié)點,尾節(jié)點,個數(shù)等。

緩存用完了怎么辦?

? ? Buffer Pool緩存池的大小是有限的,如果只是一味的添加數(shù)據(jù),無論多大的內(nèi)存都會用完,所有當(dāng)從free 鏈表中獲取緩存頁發(fā)現(xiàn)沒有的時候就應(yīng)該考慮刪除一些緩存頁數(shù)據(jù)騰出新的緩存頁

? ? ? ? LRU鏈表:這種方式是添加一個額外的鏈表(又是鏈表)管理每個緩存頁使用的頻率,每次訪問緩存頁或者從磁盤獲取數(shù)據(jù)添加到新的緩存頁時,將此緩存頁頭插法放入LRU鏈表中,需要刪除時,只需從此鏈表尾部刪除即可。

????????LRU存在的問題:Innodb提供了一個貼心的服務(wù)稱為預(yù)讀,就是處理服務(wù)后可能會讀取某些頁面,將其加載到緩存頁。預(yù)讀的作用是為了提高語句的執(zhí)行效率,但如果預(yù)讀的數(shù)據(jù)用不到而又占據(jù)了LRU的頭,那么緩存命中率會降低;其二,如果查詢不走索引,那么會進行全表掃描,緩存數(shù)據(jù)會被全部替換,其他查詢也是如此的話Buffer Pool中所有數(shù)據(jù)又會被重新?lián)Q一次,大大降低了緩存的命中率。

? ? ? ? 解決方案:Innodb設(shè)計LRU鏈表的時候 ,把此鏈表分成了兩段,分別是:一部分存儲使用頻率非常高的數(shù)據(jù),這一部分鏈表稱為熱數(shù)據(jù)(young);另一部分存儲使用頻率不是很高的數(shù)據(jù),稱為冷數(shù)據(jù)(old),二者的長度不是固定不變的,按照某個比例設(shè)置;參數(shù)innodb_old_block_pct確定冷數(shù)據(jù)所占的比例;這種設(shè)計對以上問題解決方式是:對于預(yù)讀,數(shù)據(jù)放在冷數(shù)據(jù)部分,不占用熱數(shù)據(jù)部分,這樣內(nèi)存不足時刪除數(shù)據(jù)不影響熱數(shù)據(jù)的處理,對于全表掃描,首次把數(shù)據(jù)放在old區(qū)域后,可能馬上被再次訪問,放入到了young區(qū),這樣仍然會把young區(qū)域的數(shù)據(jù)頂下去,防止這種情況出現(xiàn),需要一個時間判斷下加入old區(qū)這個數(shù)據(jù)時間和再次訪問時間間隔,如果很短,那么不把數(shù)據(jù)放入young區(qū),避免了去頂除young數(shù)據(jù),這個時間間隔設(shè)置的名稱就是innodb_old_blocks_time;可以進行修改。

????將LRU鏈表分成young和old段,再加上innodb_old_blocks_time的設(shè)置,可以降低預(yù)讀和全表表掃描造成的緩存命中率低的問題。

進一步優(yōu)化

????如果每次訪問的數(shù)據(jù)都會被移動到LRU鏈表的頭部,那么開銷也是有點大的,比如兩個頁被交替訪問,也就是交替放到LRU頭部,這種頻繁在young區(qū)內(nèi)部的移動有點浪費,Innodb規(guī)定訪問的young中的數(shù)據(jù)位于young區(qū)的后1/4時才會進行移動到鏈表頭的操作,降低了鏈表頻繁調(diào)整的頻率,提高了性能。? ??

刷新臟頁到磁盤

????后臺有一個線程負(fù)責(zé)過一段時間把臟頁刷新到磁盤,這樣不影響用戶線程處理線上的請求,主要有兩種刷新路徑:

? ? ? ? 從LRU鏈表冷數(shù)據(jù)中刷新一部分到磁盤:后臺線程從LRU尾部開始掃描,遇到臟頁進行刷新到磁盤,具體掃描的頁數(shù)量通過innodb_lru_scan_depth變量進行控制。

? ? ? ? 從flush鏈表刷新一部分到磁盤:后臺線程會從flush鏈表中刷新一部分到磁盤中。

多個BufferPool實例:

多線程下訪問一個BufferPool內(nèi)存是需要加鎖處理的,在多線程環(huán)境下采用每個線程訪問一個BufferPool實例避免多個線程操作一塊內(nèi)存導(dǎo)致鎖競爭。? ? 通過innodb_buffer_pool_instanc設(shè)置個數(shù)。

? ??????

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容