一隙弛、前言
下面讓我們就一起看下,當(dāng)你執(zhí)行CURD時(shí)狞山,InnoDB的Buffer Pool中都發(fā)生了什么全闷!以及Buffer Pool的優(yōu)化!
二萍启、Let‘s go
你知道的总珠,MySQL對(duì)數(shù)據(jù)的增刪改查都是內(nèi)存中完成的,這塊內(nèi)存就是Buffer Pool勘纯。
你可以像下面這樣查看下你的MySQL的Buffer的Buffer Pool的默認(rèn)大小
上圖中的0.125單位為GB局服,轉(zhuǎn)換成MB就是 1024* 1/8 = 128MB
總結(jié)來說,就是MySQL啟動(dòng)后就會(huì)為我們初始化好這塊Buffer Pool驳遵。如下圖:
你可以看著上圖腌逢,然后讀下面這段話:
MySQL以數(shù)據(jù)頁為單位,從磁盤中讀取數(shù)據(jù)超埋。數(shù)據(jù)頁被讀取到內(nèi)存中搏讶,所謂的內(nèi)存其實(shí)就是Buffer Pool。
Buffer Pool中維護(hù)的數(shù)據(jù)結(jié)構(gòu)是緩存頁霍殴,而且每個(gè)緩存頁都有它對(duì)應(yīng)的描述信息媒惕。
由于MySQL剛啟動(dòng),還沒有從磁盤中讀取任何數(shù)據(jù)頁到內(nèi)存(Buffer Pool)中来庭,那此時(shí)Buffer Pool中所有的緩存頁其實(shí)都是空的妒蔚。
除了緩存頁之外,你還能看到Buffer Pool中存在三個(gè)雙向鏈表月弛。分別是FreeList肴盏、LRUList以及FlushList。這三個(gè)雙向鏈表中維護(hù)著緩存頁的描述信息帽衙。
三菜皂、好,假設(shè)你讀取出來了1個(gè)數(shù)據(jù)頁
當(dāng)你通過select讀取出一個(gè)數(shù)據(jù)頁之后厉萝,是需要將這個(gè)數(shù)據(jù)頁加載進(jìn)Buffer Pool中的緩存頁中的恍飘。
那問題來了,MySQL怎么知道該將你讀取出來的數(shù)據(jù)頁存放在那個(gè)緩存頁中呢谴垫?相信你看了上圖應(yīng)該也能想到答案了章母。FreeList這個(gè)雙向鏈表不是存放了空閑的緩存頁的描述信息嗎?那從FreeList中取出一個(gè)空間緩存頁的描述信息不就好了翩剪?于是得到了下面這張圖:
啰嗦一點(diǎn):對(duì)這張圖稍微做一下解讀:
InnoDB會(huì)將你讀取出來的數(shù)據(jù)頁加載進(jìn)Buffer Pool中的緩存頁中乳怎,然后緩存頁的描述信息也會(huì)被維護(hù)進(jìn)LRU鏈表中。鏈表做了冷熱數(shù)據(jù)分離優(yōu)化前弯,5/8的區(qū)域是熱數(shù)據(jù)區(qū)域蚪缀,3/8的區(qū)域算是冷數(shù)據(jù)區(qū)域焦辅。(本質(zhì)上它們都是雙向鏈表),而你新讀取的數(shù)據(jù)頁會(huì)被放在冷數(shù)據(jù)區(qū)的靠前的位置上椿胯。
如果你將該數(shù)據(jù)頁讀取出來加載進(jìn)緩存頁中后,間隔沒到1s剃根,就使用該緩存頁哩盲。那么InnoDB是不會(huì)將這個(gè)描述信息移動(dòng)到5/8的熱數(shù)據(jù)區(qū)域的。
但是當(dāng)超過1s后狈醉,你又去讀這個(gè)數(shù)據(jù)頁廉油。那這個(gè)數(shù)據(jù)頁的描述信息就會(huì)被放到熱數(shù)據(jù)區(qū)域。如下圖:
四苗傅、假設(shè)你一次性讀取出來了好多數(shù)據(jù)頁
白日夢(mèng)在第 6 篇文章中跟大家分享過抒线,MySQL是存在預(yù)讀機(jī)制的,感興趣可關(guān)注公眾號(hào)閱讀渣慕。
假設(shè)觸發(fā)了MySQL的預(yù)讀機(jī)制嘶炭。一次性從磁盤中讀取來N多個(gè)緩存頁。會(huì)得到下面這張圖:
因?yàn)榘l(fā)生了預(yù)讀逊桦,所以你的一次磁盤IO讀出了大量的數(shù)據(jù)頁眨猎,但是這些數(shù)據(jù)頁中很可能是有一些是你根本不需要的,僅僅是預(yù)讀把它們級(jí)聯(lián)查出來了强经。這時(shí)按老規(guī)矩睡陪,從FreeList中找到空閑的緩存頁信息,然后將其從FreeList中移除匿情。根據(jù)找到的空閑緩存頁的描述信息兰迫,將從磁盤中讀取出來的數(shù)據(jù)頁加載進(jìn)去。相應(yīng)的該緩存頁的描述信息也會(huì)被維護(hù)進(jìn)LRU鏈表的冷數(shù)據(jù)區(qū)域炬称。
這時(shí)你就會(huì)發(fā)現(xiàn)這種冷熱數(shù)據(jù)分離的機(jī)制多么妙汁果!即使發(fā)生了預(yù)讀又怎么樣?根本沒有機(jī)會(huì)將熱數(shù)據(jù)區(qū)的描述信息1擠下去玲躯。當(dāng)內(nèi)存不夠用了需要將部分緩存頁刷新到磁盤中時(shí)须鼎,那就從冷數(shù)據(jù)區(qū)域開始刷新好了,反正他們本來就不經(jīng)常被使用府蔗。
同樣的晋控,當(dāng)你超過1s后又訪問了冷數(shù)據(jù)區(qū)的緩存頁,比如訪問了緩存頁66和數(shù)據(jù)頁67姓赤,該緩存頁對(duì)應(yīng)的描述信息是會(huì)被提升到熱數(shù)據(jù)區(qū)赡译,于是有了下面這張圖:
那,如果你訪問上圖中的數(shù)據(jù)頁67不铆,它會(huì)移動(dòng)到描述信息66所在節(jié)點(diǎn)的前面去嗎蝌焚?
其實(shí)MySQL的LRU鏈表做了優(yōu)化裹唆,數(shù)據(jù)67是不會(huì)往前跑的。
五只洒、假設(shè)你修改了某數(shù)據(jù)頁
假設(shè)你執(zhí)行了update xxx set xxx where id in (xxx,xxx,xxx,xxx)许帐;
而符合條件的數(shù)據(jù)行恰巧就在描述信息1、描述信息66毕谴、描述信息67所指向的緩存頁中成畦,那BufferPool中會(huì)發(fā)生什么呢?
如下圖:
你會(huì)看到涝开,被你修改了的緩存頁的描述信息循帐,被添加到了FlushList這個(gè)雙向鏈表中。
想必看到這里你已經(jīng)知道了舀武,原來FlushList中的節(jié)點(diǎn)存放就是被修改了臟數(shù)據(jù)頁的描述信息塊拄养。
隨著MySQL被使用的時(shí)間越來越長(zhǎng),BufferPool的大小就越來越小银舱。等它不夠用的時(shí)候瘪匿,就會(huì)將部分LRU中的數(shù)據(jù)頁描述信息移除出去,這時(shí)如果發(fā)現(xiàn)被移除出來的數(shù)據(jù)頁在FLushList中寻馏,就會(huì)觸發(fā)fsync的操作柿顶,觸發(fā)隨機(jī)寫磁盤。如果該數(shù)據(jù)頁是干凈的操软,那移除出去就好了嘁锯。其他也不用干啥。
舉個(gè)例子:假設(shè)需要將描述信息66聂薪、描述信息67指向的緩存頁落盤家乘。會(huì)得到下面這張腦圖:
描述信息66、67指向的緩存頁被刷新進(jìn)磁盤藏澳。 同時(shí)從FlushList中將其移除仁锯,然后存入FreeList中。完成一個(gè)循環(huán)
當(dāng)然翔悠,將臟數(shù)據(jù)頁刷新進(jìn)磁盤的時(shí)機(jī)除了上圖中說的還有好多種情況业崖,白日夢(mèng)在上一篇文章中有分享⌒畛睿可關(guān)注公眾號(hào)查看哦
下面再看一下關(guān)于Buffer Pool的設(shè)置和相關(guān)的優(yōu)化双炕。
六、配置Buffer Pool的大小
buffer pool越大撮抓,MySQL的性能就越強(qiáng)悍妇斤。你可以像下面這樣配置Buffer Pool的大小。
mysql> SET GLOBAL innodb_buffer_pool_size=402653184;
七、配置多個(gè)Buffer Pool的實(shí)例
你可以為MySQL實(shí)例配置多個(gè)Buffer Pool站超,每個(gè)Buffer Pool各自負(fù)責(zé)管理一部分緩存頁荸恕,并且有自己獨(dú)立的LRU、Free死相、Flush鏈表融求。
當(dāng)有多線程并發(fā)請(qǐng)求過來時(shí),線程可以在不同的Buffer Pool中執(zhí)行自己的操作算撮,MySQL性能就會(huì)得到很大的提升
在my.d中進(jìn)行配置
[server]
innodb_buffer_pool_size = xxx
innodb_buffer_pool_instances = 4
意思是將總?cè)萘繛閤xx的buffer pool劃分成4個(gè)實(shí)例生宛。每個(gè)實(shí)例都有 xxx/4 的容量。
參數(shù)innodb_buffer_pool_instances的最大值為64钮惠,并且想讓該參數(shù)生效,innodb_buffer_pool_size容量至少是1G七芭。
可以像下面這樣查看你的MySQL的Buffer Pool實(shí)例狀態(tài)素挽。
八、揭秘BufferPool的真實(shí)結(jié)構(gòu)
現(xiàn)實(shí)中Buffer Pool動(dòng)輒就占用好幾G的內(nèi)存狸驳,相對(duì)于直接申請(qǐng)幾G的內(nèi)存完成擴(kuò)容预明,MySQL有更優(yōu)雅的實(shí)現(xiàn)方式。
為了實(shí)現(xiàn)動(dòng)態(tài)調(diào)整Buffer Pool的大小耙箍。MySQL設(shè)計(jì)了chunk 機(jī)制撰糠。
可以看上圖腦補(bǔ)一下Buffer Pool 以及 Chunk長(zhǎng)什么樣。
總的來說:就是將每一個(gè) Buffer Pool Instance 更加細(xì)力度化辩昆。將Buffer Pool拆分成更小的獨(dú)立單元阅酪。
每個(gè)Buffer Pool劃分成多個(gè)chunnk,每個(gè)chunk中維護(hù)一部分緩存頁汁针、緩存頁的描述信息术辐。同屬于一個(gè)Buffer Pool的chunk共享該Buffer Pool的lru、free施无、flush鏈表辉词。
塊大小由參數(shù)innodb_buffer_pool_chunk_size控制,默認(rèn)值為 128M
該參數(shù)可以像下面這樣修改:
shell> mysqld --innodb-buffer-pool-chunk-size=134217728
或者通過配置文件自定義
[mysqld]
innodb_buffer_pool_chunk_size=134217728
九猾骡、看一看Buffer Pool相關(guān)的參數(shù)
執(zhí)行命令
> mysql show engine innodb status
十瑞躺、如何規(guī)劃你的Buffer Pool大小
推薦將Buffer Pool的總大小設(shè)置為服務(wù)器內(nèi)存的 50%~60%左右
BufferPool總大小 = (chunkSize * bufferPoolInstanceNum)*2
十一、Buffer Pool的預(yù)熱機(jī)制
這種機(jī)制實(shí)際上是想讓重啟后的MySQL快速適應(yīng)大規(guī)模的流量請(qǐng)求兴想。
InnoDB 在服務(wù)器關(guān)閉時(shí)為每個(gè)緩沖池保存一部分最近高頻使用的頁面幢哨,并在服務(wù)器啟動(dòng)時(shí)恢復(fù)這些頁面。保存多大比例的緩存頁由參數(shù)innodb_buffer_pool_dump_pct控制嫂便。
在啟動(dòng)時(shí)還原緩沖池嘱么,實(shí)際上會(huì)縮短預(yù)熱的時(shí)間。
你可以通過下面的方式配置該參數(shù)
# 通過命令
SET GLOBAL innodb_buffer_pool_dump_pct=40;
# 通過文件
[mysqld]
innodb_buffer_pool_dump_pct=40
參數(shù)innodb_buffer_pool_dump_at_shutdown控制 MySQL關(guān)閉時(shí)保存緩沖池的狀態(tài),默認(rèn)為on的狀態(tài)曼振。
啟動(dòng)參數(shù)--innodb-buffer-pool-load-at-startup 表示啟動(dòng)MySQL的時(shí)候恢復(fù)緩沖池中的狀態(tài)几迄,默認(rèn)也是開啟的。