本篇文章給大家?guī)?lái)了mysql中關(guān)于Buffer pool的相關(guān)知識(shí),其中包括了數(shù)據(jù)頁(yè)、緩存頁(yè)free鏈表、 flush鏈表、 LRU鏈表Chunk等等,希望對(duì)大家有幫助。 通過(guò)前邊的嘮叨我們知道,對(duì)于使用 設(shè)計(jì) 其中, 每個(gè)緩存頁(yè)對(duì)應(yīng)的控制信息占用的內(nèi)存大小是相同的,我們就把每個(gè)頁(yè)對(duì)應(yīng)的控制信息占用的一塊內(nèi)存稱為一個(gè) 咦?控制塊和緩存頁(yè)之間的那個(gè) 小貼士: 每個(gè)控制塊大約占用緩存頁(yè)大小的5%,在MySQL5.7.21這個(gè)版本中,每個(gè)控制塊占用的大小是808字節(jié)。而我們?cè)O(shè)置的innodb_buffer_pool_size并不包含這部分控制塊占用的內(nèi)存空間大小,也就是說(shuō)InnoDB在為Buffer Pool向操作系統(tǒng)申請(qǐng)連續(xù)的內(nèi)存空間時(shí),這片連續(xù)的內(nèi)存空間一般會(huì)比innodb_buffer_pool_size的值大5%左右。 當(dāng)我們最初啟動(dòng) 從圖中可以看出,我們?yōu)榱斯芾砗眠@個(gè) 小貼士: 鏈表基節(jié)點(diǎn)占用的內(nèi)存空間并不大,在MySQL5.7.21這個(gè)版本里,每個(gè)基節(jié)點(diǎn)只占用40字節(jié)大小。后邊我們即將介紹許多不同的鏈表,它們的基節(jié)點(diǎn)和free鏈表的基節(jié)點(diǎn)的內(nèi)存分配方式是一樣一樣的,都是單獨(dú)申請(qǐng)的一塊40字節(jié)大小的內(nèi)存空間,并不包含在為Buffer Pool申請(qǐng)的一大片連續(xù)內(nèi)存空間之內(nèi)。 有了這個(gè) 我們前邊說(shuō)過(guò),當(dāng)我們需要訪問(wèn)某個(gè)頁(yè)中的數(shù)據(jù)時(shí),就會(huì)把該頁(yè)從磁盤(pán)加載到 再回頭想想,我們其實(shí)是根據(jù) 小貼士: 啥?你別告訴我你不知道哈希表是個(gè)啥?我們這個(gè)文章不是講哈希表的,如果你不會(huì)那就去找本數(shù)據(jù)結(jié)構(gòu)的書(shū)看看吧~ 啥?外頭的書(shū)看不懂?別急,等我~ 所以我們可以用 如果我們修改了 但是如果不立即同步到磁盤(pán)的話,那之后再同步的時(shí)候我們?cè)趺粗?code>Buffer Pool中哪些頁(yè)是 緩存不夠的窘境 為了回答這個(gè)問(wèn)題,我們還需要回到我們?cè)O(shè)立 簡(jiǎn)單的LRU鏈表 管理 如果該頁(yè)不在 如果該頁(yè)已經(jīng)緩存在 也就是說(shuō):只要我們使用到某個(gè)緩存頁(yè),就把該緩存頁(yè)調(diào)整到 劃分區(qū)域的LRU鏈表 高興的太早了,上邊的這個(gè)簡(jiǎn)單的 情況一: 線性預(yù)讀 設(shè)計(jì) 小貼士: InnoDB是怎么實(shí)現(xiàn)異步讀取的呢?在Windows或者Linux平臺(tái)上,可能是直接調(diào)用操作系統(tǒng)內(nèi)核提供的AIO接口,在其它類Unix操作系統(tǒng)中,使用了一種模擬AIO接口的方式來(lái)實(shí)現(xiàn)異步讀取,其實(shí)就是讓別的線程去讀取需要預(yù)讀的頁(yè)面。如果你讀不懂上邊這段話,那也就沒(méi)必要懂了,和我們主題其實(shí)沒(méi)太多關(guān)系,你只需要知道異步讀取并不會(huì)影響到當(dāng)前工作線程的正常執(zhí)行就好了。其實(shí)這個(gè)過(guò)程涉及到操作系統(tǒng)如何處理IO以及多線程的問(wèn)題,找本操作系統(tǒng)的書(shū)看看吧,什么?操作系統(tǒng)的書(shū)寫(xiě)的都很難懂?沒(méi)關(guān)系,等我~ 隨機(jī)預(yù)讀 如果 情況二:有的小伙伴可能會(huì)寫(xiě)一些需要掃描全表的查詢語(yǔ)句(比如沒(méi)有建立合適的索引或者壓根兒沒(méi)有WHERE子句的查詢)。 掃描全表意味著什么?意味著將訪問(wèn)到該表所在的所有頁(yè)!假設(shè)這個(gè)表中記錄非常多的話,那該表會(huì)占用特別多的 總結(jié)一下上邊說(shuō)的可能降低 加載到 如果非常多的使用頻率偏低的頁(yè)被同時(shí)加載到 因?yàn)橛羞@兩種情況的存在,所以設(shè)計(jì) 一部分存儲(chǔ)使用頻率非常高的緩存頁(yè),所以這一部分鏈表也叫做 另一部分存儲(chǔ)使用頻率不是很高的緩存頁(yè),所以這一部分鏈表也叫做 為了方便大家理解,我們把示意圖做了簡(jiǎn)化,各位領(lǐng)會(huì)精神就好: 大家要特別注意一個(gè)事兒:我們是按照某個(gè)比例將LRU鏈表分成兩半的,不是某些節(jié)點(diǎn)固定是young區(qū)域的,某些節(jié)點(diǎn)固定是old區(qū)域的,隨著程序的運(yùn)行,某個(gè)節(jié)點(diǎn)所屬的區(qū)域也可能發(fā)生變化。那這個(gè)劃分成兩截的比例怎么確定呢?對(duì)于 從結(jié)果可以看出來(lái),默認(rèn)情況下, 這樣我們?cè)趩?dòng)服務(wù)器后, 有了這個(gè)被劃分成 針對(duì)預(yù)讀的頁(yè)面可能不進(jìn)行后續(xù)訪問(wèn)情況的優(yōu)化 設(shè)計(jì) 針對(duì)全表掃描時(shí),短時(shí)間內(nèi)訪問(wèn)大量使用頻率非常低的頁(yè)面情況的優(yōu)化 在進(jìn)行全表掃描時(shí),雖然首次被加載到 咋辦?全表掃描有一個(gè)特點(diǎn),那就是它的執(zhí)行頻率非常低,誰(shuí)也不會(huì)沒(méi)事兒老在那寫(xiě)全表掃描的語(yǔ)句玩,而且在執(zhí)行全表掃描的過(guò)程中,即使某個(gè)頁(yè)面中有很多條記錄,也就是去多次訪問(wèn)這個(gè)頁(yè)面所花費(fèi)的時(shí)間也是非常少的。所以我們只需要規(guī)定,在對(duì)某個(gè)處在 這個(gè) 綜上所述,正是因?yàn)閷?code>LRU鏈表劃分為 更進(jìn)一步優(yōu)化LRU鏈表 小貼士: 我們之前介紹隨機(jī)預(yù)讀的時(shí)候曾說(shuō),如果Buffer Pool中有某個(gè)區(qū)的13個(gè)連續(xù)頁(yè)面就會(huì)觸發(fā)隨機(jī)預(yù)讀,這其實(shí)是不嚴(yán)謹(jǐn)?shù)模ú恍业氖荕ySQL文檔就是這么說(shuō)的[攤手]),其實(shí)還要求這13個(gè)頁(yè)面是非常熱的頁(yè)面,所謂的非常熱,指的是這些頁(yè)面在整個(gè)young區(qū)域的頭1/4處。 還有沒(méi)有什么別的針對(duì)緩存的重要性
InnoDB
作為存儲(chǔ)引擎的表來(lái)說(shuō),不管是用于存儲(chǔ)用戶數(shù)據(jù)的索引(包括聚簇索引和二級(jí)索引),還是各種系統(tǒng)數(shù)據(jù),都是以頁(yè)
的形式存放在表空間
中的,而所謂的表空間
只不過(guò)是InnoDB
對(duì)文件系統(tǒng)上一個(gè)或幾個(gè)實(shí)際文件的抽象,也就是說(shuō)我們的數(shù)據(jù)說(shuō)到底還是存儲(chǔ)在磁盤(pán)上的。但是各位也都知道,磁盤(pán)的速度慢的跟烏龜一樣,怎么能配得上“快如風(fēng),疾如電”的CPU
呢?所以InnoDB
存儲(chǔ)引擎在處理客戶端的請(qǐng)求時(shí),當(dāng)需要訪問(wèn)某個(gè)頁(yè)的數(shù)據(jù)時(shí),就會(huì)把完整的頁(yè)的數(shù)據(jù)全部加載到內(nèi)存中,也就是說(shuō)即使我們只需要訪問(wèn)一個(gè)頁(yè)的一條記錄,那也需要先把整個(gè)頁(yè)的數(shù)據(jù)加載到內(nèi)存中。將整個(gè)頁(yè)加載到內(nèi)存中后就可以進(jìn)行讀寫(xiě)訪問(wèn)了,在進(jìn)行完讀寫(xiě)訪問(wèn)之后并不著急把該頁(yè)對(duì)應(yīng)的內(nèi)存空間釋放掉,而是將其緩存
起來(lái),這樣將來(lái)有請(qǐng)求再次訪問(wèn)該頁(yè)面時(shí),就可以省去磁盤(pán)IO
的開(kāi)銷了。InnoDB的Buffer Pool
啥是個(gè)Buffer Pool
InnoDB
的大叔為了緩存磁盤(pán)中的頁(yè),在MySQL
服務(wù)器啟動(dòng)的時(shí)候就向操作系統(tǒng)申請(qǐng)了一片連續(xù)的內(nèi)存,他們給這片內(nèi)存起了個(gè)名,叫做Buffer Pool
(中文名是緩沖池
)。那它有多大呢?這個(gè)其實(shí)看我們機(jī)器的配置,如果你是土豪,你有512G
內(nèi)存,你分配個(gè)幾百G作為Buffer Pool
也可以啊,當(dāng)然你要是沒(méi)那么有錢(qián),設(shè)置小點(diǎn)也行呀~ 默認(rèn)情況下Buffer Pool
只有128M
大小。當(dāng)然如果你嫌棄這個(gè)128M
太大或者太小,可以在啟動(dòng)服務(wù)器的時(shí)候配置innodb_buffer_pool_size
參數(shù)的值,它表示Buffer Pool
的大小,就像這樣:[server] innodb_buffer_pool_size = 268435456
268435456
的單位是字節(jié),也就是我指定Buffer Pool
的大小為256M
。需要注意的是,Buffer Pool
也不能太小,最小值為5M
(當(dāng)小于該值時(shí)會(huì)自動(dòng)設(shè)置成5M
)。Buffer Pool內(nèi)部組成
Buffer Pool
中默認(rèn)的緩存頁(yè)大小和在磁盤(pán)上默認(rèn)的頁(yè)大小是一樣的,都是16KB
。為了更好的管理這些在Buffer Pool
中的緩存頁(yè),設(shè)計(jì)InnoDB
的大叔為每一個(gè)緩存頁(yè)都創(chuàng)建了一些所謂的控制信息
,這些控制信息包括該頁(yè)所屬的表空間編號(hào)、頁(yè)號(hào)、緩存頁(yè)在Buffer Pool
中的地址、鏈表節(jié)點(diǎn)信息、一些鎖信息以及LSN
信息(鎖和LSN
我們之后會(huì)具體嘮叨,現(xiàn)在可以先忽略),當(dāng)然還有一些別的控制信息,我們這就不全嘮叨一遍了,挑重要的說(shuō)嘛~控制塊
吧,控制塊和緩存頁(yè)是一一對(duì)應(yīng)的,它們都被存放到 Buffer Pool 中,其中控制塊被存放到 Buffer Pool 的前邊,緩存頁(yè)被存放到 Buffer Pool 后邊,所以整個(gè)Buffer Pool
對(duì)應(yīng)的內(nèi)存空間看起來(lái)就是這樣的:碎片
是個(gè)什么玩意兒?你想想啊,每一個(gè)控制塊都對(duì)應(yīng)一個(gè)緩存頁(yè),那在分配足夠多的控制塊和緩存頁(yè)后,可能剩余的那點(diǎn)兒空間不夠一對(duì)控制塊和緩存頁(yè)的大小,自然就用不到嘍,這個(gè)用不到的那點(diǎn)兒內(nèi)存空間就被稱為碎片
了。當(dāng)然,如果你把Buffer Pool
的大小設(shè)置的剛剛好的話,也可能不會(huì)產(chǎn)生碎片
~
free鏈表的管理
MySQL
服務(wù)器的時(shí)候,需要完成對(duì)Buffer Pool
的初始化過(guò)程,就是先向操作系統(tǒng)申請(qǐng)Buffer Pool
的內(nèi)存空間,然后把它劃分成若干對(duì)控制塊和緩存頁(yè)。但是此時(shí)并沒(méi)有真實(shí)的磁盤(pán)頁(yè)被緩存到Buffer Pool
中(因?yàn)檫€沒(méi)有用到),之后隨著程序的運(yùn)行,會(huì)不斷的有磁盤(pán)上的頁(yè)被緩存到Buffer Pool
中。那么問(wèn)題來(lái)了,從磁盤(pán)上讀取一個(gè)頁(yè)到Buffer Pool
中的時(shí)候該放到哪個(gè)緩存頁(yè)的位置呢?或者說(shuō)怎么區(qū)分Buffer Pool
中哪些緩存頁(yè)是空閑的,哪些已經(jīng)被使用了呢?我們最好在某個(gè)地方記錄一下Buffer Pool中哪些緩存頁(yè)是可用的,這個(gè)時(shí)候緩存頁(yè)對(duì)應(yīng)的控制塊
就派上大用場(chǎng)了,我們可以把所有空閑的緩存頁(yè)對(duì)應(yīng)的控制塊作為一個(gè)節(jié)點(diǎn)放到一個(gè)鏈表中,這個(gè)鏈表也可以被稱作free鏈表
(或者說(shuō)空閑鏈表)。剛剛完成初始化的Buffer Pool
中所有的緩存頁(yè)都是空閑的,所以每一個(gè)緩存頁(yè)對(duì)應(yīng)的控制塊都會(huì)被加入到free鏈表
中,假設(shè)該Buffer Pool
中可容納的緩存頁(yè)數(shù)量為n
,那增加了free鏈表
的效果圖就是這樣的:free鏈表
,特意為這個(gè)鏈表定義了一個(gè)基節(jié)點(diǎn)
,里邊兒包含著鏈表的頭節(jié)點(diǎn)地址,尾節(jié)點(diǎn)地址,以及當(dāng)前鏈表中節(jié)點(diǎn)的數(shù)量等信息。這里需要注意的是,鏈表的基節(jié)點(diǎn)占用的內(nèi)存空間并不包含在為Buffer Pool
申請(qǐng)的一大片連續(xù)內(nèi)存空間之內(nèi),而是單獨(dú)申請(qǐng)的一塊內(nèi)存空間。
free鏈表
之后事兒就好辦了,每當(dāng)需要從磁盤(pán)中加載一個(gè)頁(yè)到Buffer Pool
中時(shí),就從free鏈表
中取一個(gè)空閑的緩存頁(yè),并且把該緩存頁(yè)對(duì)應(yīng)的控制塊
的信息填上(就是該頁(yè)所在的表空間、頁(yè)號(hào)之類的信息),然后把該緩存頁(yè)對(duì)應(yīng)的free鏈表
節(jié)點(diǎn)從鏈表中移除,表示該緩存頁(yè)已經(jīng)被使用了~緩存頁(yè)的哈希處理
Buffer Pool
中,如果該頁(yè)已經(jīng)在Buffer Pool
中的話直接使用就可以了。那么問(wèn)題也就來(lái)了,我們?cè)趺粗涝擁?yè)在不在Buffer Pool
中呢?難不成需要依次遍歷Buffer Pool
中各個(gè)緩存頁(yè)么?一個(gè)Buffer Pool
中的緩存頁(yè)這么多都遍歷完豈不是要累死?表空間號(hào) + 頁(yè)號(hào)
來(lái)定位一個(gè)頁(yè)的,也就相當(dāng)于表空間號(hào) + 頁(yè)號(hào)
是一個(gè)key
,緩存頁(yè)
就是對(duì)應(yīng)的value
,怎么通過(guò)一個(gè)key
來(lái)快速找著一個(gè)value
呢?哈哈,那肯定是哈希表嘍~
表空間號(hào) + 頁(yè)號(hào)
作為key
,緩存頁(yè)
作為value
創(chuàng)建一個(gè)哈希表,在需要訪問(wèn)某個(gè)頁(yè)的數(shù)據(jù)時(shí),先從哈希表中根據(jù)表空間號(hào) + 頁(yè)號(hào)
看看有沒(méi)有對(duì)應(yīng)的緩存頁(yè),如果有,直接使用該緩存頁(yè)就好,如果沒(méi)有,那就從free鏈表
中選一個(gè)空閑的緩存頁(yè),然后把磁盤(pán)中對(duì)應(yīng)的頁(yè)加載到該緩存頁(yè)的位置。flush鏈表的管理
Buffer Pool
中某個(gè)緩存頁(yè)的數(shù)據(jù),那它就和磁盤(pán)上的頁(yè)不一致了,這樣的緩存頁(yè)也被稱為臟頁(yè)
(英文名:dirty page
)。當(dāng)然,最簡(jiǎn)單的做法就是每發(fā)生一次修改就立即同步到磁盤(pán)上對(duì)應(yīng)的頁(yè)上,但是頻繁的往磁盤(pán)中寫(xiě)數(shù)據(jù)會(huì)嚴(yán)重的影響程序的性能(畢竟磁盤(pán)慢的像烏龜一樣)。所以每次修改緩存頁(yè)后,我們并不著急立即把修改同步到磁盤(pán)上,而是在未來(lái)的某個(gè)時(shí)間點(diǎn)進(jìn)行同步,至于這個(gè)同步的時(shí)間點(diǎn)我們后邊會(huì)作說(shuō)明說(shuō)明的,現(xiàn)在先不用管哈~臟頁(yè)
,哪些頁(yè)從來(lái)沒(méi)被修改過(guò)呢?總不能把所有的緩存頁(yè)都同步到磁盤(pán)上吧,假如Buffer Pool
被設(shè)置的很大,比方說(shuō)300G
,那一次性同步這么多數(shù)據(jù)豈不是要慢死!所以,我們不得不再創(chuàng)建一個(gè)存儲(chǔ)臟頁(yè)的鏈表,凡是修改過(guò)的緩存頁(yè)對(duì)應(yīng)的控制塊都會(huì)作為一個(gè)節(jié)點(diǎn)加入到一個(gè)鏈表中,因?yàn)檫@個(gè)鏈表節(jié)點(diǎn)對(duì)應(yīng)的緩存頁(yè)都是需要被刷新到磁盤(pán)上的,所以也叫flush鏈表
。鏈表的構(gòu)造和free鏈表
差不多,假設(shè)某個(gè)時(shí)間點(diǎn)Buffer Pool
中的臟頁(yè)數(shù)量為n
,那么對(duì)應(yīng)的flush鏈表
就長(zhǎng)這樣:LRU鏈表的管理
Buffer Pool
對(duì)應(yīng)的內(nèi)存大小畢竟是有限的,如果需要緩存的頁(yè)占用的內(nèi)存大小超過(guò)了Buffer Pool
大小,也就是free鏈表
中已經(jīng)沒(méi)有多余的空閑緩存頁(yè)的時(shí)候豈不是很尷尬,發(fā)生了這樣的事兒該咋辦?當(dāng)然是把某些舊的緩存頁(yè)從Buffer Pool
中移除,然后再把新的頁(yè)放進(jìn)來(lái)嘍~ 那么問(wèn)題來(lái)了,移除哪些緩存頁(yè)呢?Buffer Pool
的初衷,我們就是想減少和磁盤(pán)的IO
交互,最好每次在訪問(wèn)某個(gè)頁(yè)的時(shí)候它都已經(jīng)被緩存到Buffer Pool
中了。假設(shè)我們一共訪問(wèn)了n
次頁(yè),那么被訪問(wèn)的頁(yè)已經(jīng)在緩存中的次數(shù)除以n
就是所謂的緩存命中率
,我們的期望就是讓緩存命中率
越高越好~ 從這個(gè)角度出發(fā),回想一下我們的微信聊天列表,排在前邊的都是最近很頻繁使用的,排在后邊的自然就是最近很少使用的,假如列表能容納下的聯(lián)系人有限,你是會(huì)把最近很頻繁使用的留下還是最近很少使用的留下呢?廢話,當(dāng)然是留下最近很頻繁使用的了~Buffer Pool
的緩存頁(yè)其實(shí)也是這個(gè)道理,當(dāng)Buffer Pool
中不再有空閑的緩存頁(yè)時(shí),就需要淘汰掉部分最近很少使用的緩存頁(yè)。不過(guò),我們?cè)趺粗滥男┚彺骓?yè)最近頻繁使用,哪些最近很少使用呢?呵呵,神奇的鏈表再一次派上了用場(chǎng),我們可以再創(chuàng)建一個(gè)鏈表,由于這個(gè)鏈表是為了按照最近最少使用
的原則去淘汰緩存頁(yè)的,所以這個(gè)鏈表可以被稱為LRU鏈表
(LRU的英文全稱:Least Recently Used)。當(dāng)我們需要訪問(wèn)某個(gè)頁(yè)時(shí),可以這樣處理LRU鏈表
:
Buffer Pool
中,在把該頁(yè)從磁盤(pán)加載到Buffer Pool
中的緩存頁(yè)時(shí),就把該緩存頁(yè)對(duì)應(yīng)的控制塊
作為節(jié)點(diǎn)塞到鏈表的頭部。Buffer Pool
中,則直接把該頁(yè)對(duì)應(yīng)的控制塊
移動(dòng)到LRU鏈表
的頭部。LRU鏈表
的頭部,這樣LRU鏈表
尾部就是最近最少使用的緩存頁(yè)嘍~ 所以當(dāng)Buffer Pool
中的空閑緩存頁(yè)使用完時(shí),到LRU鏈表
的尾部找些緩存頁(yè)淘汰就OK啦,真簡(jiǎn)單,嘖嘖…LRU鏈表
用了沒(méi)多長(zhǎng)時(shí)間就發(fā)現(xiàn)問(wèn)題了,因?yàn)榇嬖谶@兩種比較尷尬的情況:
InnoDB
提供了一個(gè)看起來(lái)比較貼心的服務(wù)——預(yù)讀
(英文名:read ahead
)。所謂預(yù)讀
,就是InnoDB
認(rèn)為執(zhí)行當(dāng)前的請(qǐng)求可能之后會(huì)讀取某些頁(yè)面,就預(yù)先把它們加載到Buffer Pool
中。根據(jù)觸發(fā)方式的不同,預(yù)讀
又可以細(xì)分為下邊兩種:
InnoDB
的大叔提供了一個(gè)系統(tǒng)變量innodb_read_ahead_threshold
,如果順序訪問(wèn)了某個(gè)區(qū)(extent
)的頁(yè)面超過(guò)這個(gè)系統(tǒng)變量的值,就會(huì)觸發(fā)一次異步
讀取下一個(gè)區(qū)中全部的頁(yè)面到Buffer Pool
的請(qǐng)求,注意異步
讀取意味著從磁盤(pán)中加載這些被預(yù)讀的頁(yè)面并不會(huì)影響到當(dāng)前工作線程的正常執(zhí)行。這個(gè)innodb_read_ahead_threshold
系統(tǒng)變量的值默認(rèn)是56
,我們可以在服務(wù)器啟動(dòng)時(shí)通過(guò)啟動(dòng)參數(shù)或者服務(wù)器運(yùn)行過(guò)程中直接調(diào)整該系統(tǒng)變量的值,不過(guò)它是一個(gè)全局變量,注意使用SET GLOBAL
命令來(lái)修改哦。
Buffer Pool
中已經(jīng)緩存了某個(gè)區(qū)的13個(gè)連續(xù)的頁(yè)面,不論這些頁(yè)面是不是順序讀取的,都會(huì)觸發(fā)一次異步
讀取本區(qū)中所有其的頁(yè)面到Buffer Pool
的請(qǐng)求。設(shè)計(jì)InnoDB
的大叔同時(shí)提供了innodb_random_read_ahead
系統(tǒng)變量,它的默認(rèn)值為OFF
,也就意味著InnoDB
并不會(huì)默認(rèn)開(kāi)啟隨機(jī)預(yù)讀的功能,如果我們想開(kāi)啟該功能,可以通過(guò)修改啟動(dòng)參數(shù)或者直接使用SET GLOBAL
命令把該變量的值設(shè)置為ON
。預(yù)讀
本來(lái)是個(gè)好事兒,如果預(yù)讀到Buffer Pool
中的頁(yè)成功的被使用到,那就可以極大的提高語(yǔ)句執(zhí)行的效率??墒侨绻貌坏侥??這些預(yù)讀的頁(yè)都會(huì)放到LRU
鏈表的頭部,但是如果此時(shí)Buffer Pool
的容量不太大而且很多預(yù)讀的頁(yè)面都沒(méi)有用到的話,這就會(huì)導(dǎo)致處在LRU鏈表
尾部的一些緩存頁(yè)會(huì)很快的被淘汰掉,也就是所謂的劣幣驅(qū)逐良幣
,會(huì)大大降低緩存命中率。頁(yè)
,當(dāng)需要訪問(wèn)這些頁(yè)時(shí),會(huì)把它們統(tǒng)統(tǒng)都加載到Buffer Pool
中,這也就意味著吧唧一下,Buffer Pool
中的所有頁(yè)都被換了一次血,其他查詢語(yǔ)句在執(zhí)行時(shí)又得執(zhí)行一次從磁盤(pán)加載到Buffer Pool
的操作。而這種全表掃描的語(yǔ)句執(zhí)行的頻率也不高,每次執(zhí)行都要把Buffer Pool
中的緩存頁(yè)換一次血,這嚴(yán)重的影響到其他查詢對(duì) Buffer Pool
的使用,從而大大降低了緩存命中率。Buffer Pool
的兩種情況:
Buffer Pool
中的頁(yè)不一定被用到。Buffer Pool
時(shí),可能會(huì)把那些使用頻率非常高的頁(yè)從Buffer Pool
中淘汰掉。InnoDB
的大叔把這個(gè)LRU鏈表
按照一定比例分成兩截,分別是:
熱數(shù)據(jù)
,或者稱young區(qū)域
。冷數(shù)據(jù)
,或者稱old區(qū)域
。InnoDB
存儲(chǔ)引擎來(lái)說(shuō),我們可以通過(guò)查看系統(tǒng)變量innodb_old_blocks_pct
的值來(lái)確定old
區(qū)域在LRU鏈表
中所占的比例,比方說(shuō)這樣:mysql> SHOW VARIABLES LIKE 'innodb_old_blocks_pct'; +-----------------------+-------+ | Variable_name | Value | +-----------------------+-------+ | innodb_old_blocks_pct | 37 | +-----------------------+-------+ 1 row in set (0.01 sec)
old
區(qū)域在LRU鏈表
中所占的比例是37%
,也就是說(shuō)old
區(qū)域大約占LRU鏈表
的3/8
。這個(gè)比例我們是可以設(shè)置的,我們可以在啟動(dòng)時(shí)修改innodb_old_blocks_pct
參數(shù)來(lái)控制old
區(qū)域在LRU鏈表
中所占的比例,比方說(shuō)這樣修改配置文件:[server] innodb_old_blocks_pct = 40
old
區(qū)域占LRU鏈表
的比例就是40%
。當(dāng)然,如果在服務(wù)器運(yùn)行期間,我們也可以修改這個(gè)系統(tǒng)變量的值,不過(guò)需要注意的是,這個(gè)系統(tǒng)變量屬于全局變量
,一經(jīng)修改,會(huì)對(duì)所有客戶端生效,所以我們只能這樣修改:SET GLOBAL innodb_old_blocks_pct = 40;
young
和old
區(qū)域的LRU
鏈表之后,設(shè)計(jì)InnoDB
的大叔就可以針對(duì)我們上邊提到的兩種可能降低緩存命中率的情況進(jìn)行優(yōu)化了:
InnoDB
的大叔規(guī)定,當(dāng)磁盤(pán)上的某個(gè)頁(yè)面在初次加載到Buffer Pool中的某個(gè)緩存頁(yè)時(shí),該緩存頁(yè)對(duì)應(yīng)的控制塊會(huì)被放到old區(qū)域的頭部。這樣針對(duì)預(yù)讀到Buffer Pool
卻不進(jìn)行后續(xù)訪問(wèn)的頁(yè)面就會(huì)被逐漸從old
區(qū)域逐出,而不會(huì)影響young
區(qū)域中被使用比較頻繁的緩存頁(yè)。Buffer Pool
的頁(yè)被放到了old
區(qū)域的頭部,但是后續(xù)會(huì)被馬上訪問(wèn)到,每次進(jìn)行訪問(wèn)的時(shí)候又會(huì)把該頁(yè)放到young
區(qū)域的頭部,這樣仍然會(huì)把那些使用頻率比較高的頁(yè)面給頂下去。有同學(xué)會(huì)想:可不可以在第一次訪問(wèn)該頁(yè)面時(shí)不將其從old
區(qū)域移動(dòng)到young
區(qū)域的頭部,后續(xù)訪問(wèn)時(shí)再將其移動(dòng)到young
區(qū)域的頭部?;卮鹗牵盒胁煌?!因?yàn)樵O(shè)計(jì)InnoDB
的大叔規(guī)定每次去頁(yè)面中讀取一條記錄時(shí),都算是訪問(wèn)一次頁(yè)面,而一個(gè)頁(yè)面中可能會(huì)包含很多條記錄,也就是說(shuō)讀取完某個(gè)頁(yè)面的記錄就相當(dāng)于訪問(wèn)了這個(gè)頁(yè)面好多次。old
區(qū)域的緩存頁(yè)進(jìn)行第一次訪問(wèn)時(shí)就在它對(duì)應(yīng)的控制塊中記錄下來(lái)這個(gè)訪問(wèn)時(shí)間,如果后續(xù)的訪問(wèn)時(shí)間與第一次訪問(wèn)的時(shí)間在某個(gè)時(shí)間間隔內(nèi),那么該頁(yè)面就不會(huì)被從old區(qū)域移動(dòng)到y(tǒng)oung區(qū)域的頭部,否則將它移動(dòng)到y(tǒng)oung區(qū)域的頭部。上述的這個(gè)間隔時(shí)間是由系統(tǒng)變量innodb_old_blocks_time
控制的,你看:mysql> SHOW VARIABLES LIKE 'innodb_old_blocks_time'; +------------------------+-------+ | Variable_name | Value | +------------------------+-------+ | innodb_old_blocks_time | 1000 | +------------------------+-------+ 1 row in set (0.01 sec)
innodb_old_blocks_time
的默認(rèn)值是1000
,它的單位是毫秒,也就意味著對(duì)于從磁盤(pán)上被加載到LRU
鏈表的old
區(qū)域的某個(gè)頁(yè)來(lái)說(shuō),如果第一次和最后一次訪問(wèn)該頁(yè)面的時(shí)間間隔小于1s
(很明顯在一次全表掃描的過(guò)程中,多次訪問(wèn)一個(gè)頁(yè)面中的時(shí)間不會(huì)超過(guò)1s
),那么該頁(yè)是不會(huì)被加入到young
區(qū)域的~ 當(dāng)然,像innodb_old_blocks_pct
一樣,我們也可以在服務(wù)器啟動(dòng)或運(yùn)行時(shí)設(shè)置innodb_old_blocks_time
的值,這里就不贅述了,你自己試試吧~ 這里需要注意的是,如果我們把innodb_old_blocks_time
的值設(shè)置為0
,那么每次我們?cè)L問(wèn)一個(gè)頁(yè)面時(shí)就會(huì)把該頁(yè)面放到young
區(qū)域的頭部。young
和old
區(qū)域這兩個(gè)部分,又添加了innodb_old_blocks_time
這個(gè)系統(tǒng)變量,才使得預(yù)讀機(jī)制和全表掃描造成的緩存命中率降低的問(wèn)題得到了遏制,因?yàn)橛貌坏降念A(yù)讀頁(yè)面以及全表掃描的頁(yè)面都只會(huì)被放到old
區(qū)域,而不影響young
區(qū)域中的緩存頁(yè)。LRU鏈表
這就說(shuō)完了么?沒(méi)有,早著呢~ 對(duì)于young
區(qū)域的緩存頁(yè)來(lái)說(shuō),我們每次訪問(wèn)一個(gè)緩存頁(yè)就要把它移動(dòng)到LRU鏈表
的頭部,這樣開(kāi)銷是不是太大啦,畢竟在young
區(qū)域的緩存頁(yè)都是熱點(diǎn)數(shù)據(jù),也就是可能被經(jīng)常訪問(wèn)的,這樣頻繁的對(duì)LRU鏈表
進(jìn)行節(jié)點(diǎn)移動(dòng)操作是不是不太好?。渴堑?,為了解決這個(gè)問(wèn)題其實(shí)我們還可以提出一些優(yōu)化策略,比如只有被訪問(wèn)的緩存頁(yè)位于young
區(qū)域的1/4
的后邊,才會(huì)被移動(dòng)到LRU鏈表
頭部,這樣就可以降低調(diào)整LRU鏈表
的頻率,從而提升性能(也就是說(shuō)如果某個(gè)緩存頁(yè)對(duì)應(yīng)的節(jié)點(diǎn)在young
區(qū)域的1/4
中,再次訪問(wèn)該緩存頁(yè)時(shí)也不會(huì)將其移動(dòng)到LRU
鏈表頭部)。
LRU鏈表
的優(yōu)化措施呢?當(dāng)然有啊,你要是好好學(xué),寫(xiě)篇論文,寫(xiě)本書(shū)都不是問(wèn)題,可是這畢竟是一個(gè)介紹MySQL
基礎(chǔ)知識(shí)的文章,再說(shuō)多了篇幅就受不了了,也影響大家的閱讀體驗(yàn),所以適可而止,想了解
深入了解MySQL原理篇之Buffer pool(圖文詳解)
相關(guān)推薦
- 海外廣告聯(lián)盟評(píng)測(cè):日付平臺(tái)與CPV模式如何高效變現(xiàn)?
- 華納云618大促,166元買(mǎi)8H16G5M香港cn2云服務(wù)器,大帶寬服務(wù)器688起
- RAKsmart防護(hù)配置實(shí)戰(zhàn):10Gbps套餐部署指南
- 什么是遞歸解析服務(wù)器?與其他DNS服務(wù)器有啥區(qū)別
- 什么是遞歸解析服務(wù)器?與其他DNS服務(wù)器有啥區(qū)別
- 如何利用RAKsmart服務(wù)器實(shí)現(xiàn)高效多站點(diǎn)部署方案
- 華納云香港高防服務(wù)器150G防御4.6折促銷,低至6888元/月,CN2大帶寬直連清洗,終身循環(huán)折扣
- RakSmart服務(wù)器成本優(yōu)化策略