一、前言
你知道的桌吃,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ù)頁
白日夢在第 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í)間越來越長躏筏,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ī)除了上圖中說的還有好多種情況辉阶。
下面再看一下關(guān)于Buffer Pool的設(shè)置和相關(guān)的優(yōu)化先壕。
五瘩扼、配置Buffer Pool的大小
buffer pool越大,MySQL的性能就越強(qiáng)悍垃僚。你可以像下面這樣配置Buffer Pool的大小集绰。
Copymysql> 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ā)請求過來時(shí)炫贤,線程可以在不同的Buffer Pool中執(zhí)行自己的操作溅固,MySQL性能就會(huì)得到很大的提升
在my.d中進(jìn)行配置
Copy[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ì)于直接申請幾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長什么樣。
總的來說:就是將每一個(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ù)可以像下面這樣修改:
Copyshell> mysqld --innodb-buffer-pool-chunk-size=134217728
或者通過配置文件自定義
Copy[mysqld]
innodb_buffer_pool_chunk_size=13421772
八均芽、看一看Buffer Pool相關(guān)的參數(shù)#
執(zhí)行命令
Copy> 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ī)模的流量請求骡技。
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ù)
Copy# 通過命令
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)也是開啟的。