[toc]
前言
我們已經(jīng)了解到buffer pool是通過free鏈表記載其空閑的緩存頁(yè)以及flush鏈表存放等待刷盤的臟頁(yè)的描述數(shù)據(jù)塊。當(dāng)我們從磁盤加載數(shù)據(jù)頁(yè)到buffer pool的空閑緩存頁(yè)中识颊,free鏈表就會(huì)移除一個(gè)描述數(shù)據(jù)塊淑蔚。隨著不停的增刪查改她按,空閑的緩存頁(yè)必定越來越少,當(dāng)某一個(gè)瞬間,發(fā)現(xiàn)free鏈表中已經(jīng)沒有空閑的緩存頁(yè)了缚甩,數(shù)據(jù)庫(kù)會(huì)如何操作呢柒桑?
1. 淘汰緩存頁(yè)
針對(duì)buffer pool中沒有空閑的緩存頁(yè)弊决,buffer pool就會(huì)淘汰掉一些緩存頁(yè)。
所謂的淘汰緩存頁(yè)即是選擇被修改過的緩存頁(yè)魁淳,把他們刷入磁盤中飘诗,再把緩存頁(yè)清空,讓他們重新成為空閑的緩存頁(yè)界逛。
那應(yīng)該選擇哪些緩存頁(yè)刷入磁盤昆稿?
2. 緩存命中率
假設(shè)有兩個(gè)緩存頁(yè),一個(gè)緩存頁(yè)的數(shù)據(jù)經(jīng)常被修改和查詢息拜,比如在100次請(qǐng)求中溉潭,有30次都是在查詢和修改這個(gè)緩存頁(yè)的數(shù)據(jù),就證明緩存命中率很高少欺。
另一個(gè)緩存頁(yè)岛抄,從磁盤中加載進(jìn)來后只修改或則查詢過一次,在100次請(qǐng)求中狈茉,一次都沒有查詢或修改過夫椭,就證明緩存命中率很低。
對(duì)于上述兩個(gè)緩存頁(yè)氯庆,選擇一個(gè)刷入磁盤蹭秋,毫無疑問第二個(gè)緩存頁(yè)才是最佳選擇扰付。
3. 判斷緩存頁(yè)是否常用-LRU鏈表
首先,只要從磁盤中加載數(shù)據(jù)頁(yè)到緩存頁(yè)仁讨,就會(huì)把這個(gè)緩存頁(yè)的描述數(shù)據(jù)塊放到LRU鏈表的頭部羽莺,那么,只要緩存頁(yè)有數(shù)據(jù)洞豁,他都會(huì)在LRU里盐固,而且最近被加載數(shù)據(jù)的緩存頁(yè)都會(huì)被放到LRU鏈表的頭部。
假設(shè)某個(gè)緩存頁(yè)的描述數(shù)據(jù)塊在LRU鏈表的表尾丈挟,后續(xù)只要查詢或者修改了這個(gè)緩存頁(yè)的數(shù)據(jù)刁卜,就會(huì)把這個(gè)緩存頁(yè)的描述數(shù)據(jù)塊移動(dòng)到LRU鏈表的頭部。即是說曙咽,只要最近被訪問過的緩存頁(yè)一定是在LRU鏈表的頭部蛔趴。
這時(shí),如果緩存頁(yè)沒有空閑的例朱,就把LRU鏈表尾部的緩存頁(yè)刷入磁盤孝情,就可以騰出空閑的緩存頁(yè)。
4. 簡(jiǎn)單的LRU鏈表帶來的隱患
MySQL有預(yù)讀機(jī)制洒嗤,在加載數(shù)據(jù)頁(yè)到緩存頁(yè)的時(shí)候箫荡,會(huì)提前把目標(biāo)數(shù)據(jù)頁(yè)附近的數(shù)據(jù)頁(yè)一同加載進(jìn)去。這樣就可能導(dǎo)致一個(gè)問題:剛剛加載進(jìn)來的數(shù)據(jù)頁(yè)肯定會(huì)放到LRU鏈表的頭部渔隶,就算這個(gè)緩存頁(yè)并沒有人訪問菲茬。
這時(shí)剛好要對(duì)緩存頁(yè)進(jìn)行刷盤,那后面兩個(gè)緩存頁(yè)肯定會(huì)優(yōu)先刷入磁盤派撕,而沒有人訪問的緩存頁(yè)繼續(xù)留在磁盤當(dāng)中婉弹,這樣變得非常的不合理。如果進(jìn)行全表掃描的時(shí)候终吼,那加載進(jìn)來沒人訪問的緩存頁(yè)會(huì)更多镀赌,帶來的問題會(huì)更嚴(yán)重。
MySQL的觸發(fā)預(yù)讀機(jī)制有兩種情況:
- 有一個(gè)參數(shù)是 innodb_read_ahead_threshold , 默認(rèn)值是56际跪,意思是如果順序的訪問一個(gè)區(qū)里多個(gè)數(shù)據(jù)頁(yè)商佛,訪問的數(shù)據(jù)頁(yè)數(shù)量超過了這個(gè)閾值,此時(shí)就會(huì)觸發(fā)預(yù)讀機(jī)制姆打,把下一個(gè)相鄰區(qū)中的所有緩存頁(yè)都加載到緩存里良姆。
- 如果buffer pool里緩存了一個(gè)區(qū)里的13個(gè)連續(xù)的數(shù)據(jù)頁(yè),而且這些數(shù)據(jù)頁(yè)都比較頻繁的被訪問幔戏,此時(shí)就會(huì)直接觸發(fā)預(yù)讀機(jī)制玛追,把這個(gè)區(qū)里的所有的數(shù)據(jù)頁(yè)加載到緩存里去。
第二種預(yù)讀機(jī)制默認(rèn)是關(guān)閉的。
5. 基于冷熱數(shù)據(jù)分離痊剖,優(yōu)化LRU算法
雖然MySQL會(huì)把所有加載進(jìn)來的緩存頁(yè)放在LRU鏈表中韩玩,但MySQL采取了冷熱數(shù)據(jù)分離的思想。
LRU鏈表會(huì)被拆分成為兩部分陆馁,一部分為熱數(shù)據(jù)找颓,一部分為冷數(shù)據(jù)。這個(gè)比例是由innodb_old_blocks_pct 參數(shù)控制叮贩,默認(rèn)值為37击狮,即是說,冷數(shù)據(jù)占比 37%益老。
1) 數(shù)據(jù)頁(yè)第一次加載進(jìn)來彪蓬,放在LRU鏈表的什么地方?
對(duì)于這個(gè)問題相信很多小伙伴心里都有了答案杨箭。放在冷數(shù)據(jù)區(qū)域的頭部。
2) 冷數(shù)據(jù)區(qū)域的緩存頁(yè)什么時(shí)候放入熱數(shù)據(jù)區(qū)域储狭?
我猜很多下同學(xué)認(rèn)為只要對(duì)冷數(shù)據(jù)區(qū)域的緩存頁(yè)進(jìn)行訪問就會(huì)把緩存區(qū)也放入熱數(shù)據(jù)區(qū)域互婿。
其實(shí)這個(gè)并不合理。假設(shè)1秒內(nèi)對(duì)這個(gè)緩存頁(yè)進(jìn)行了訪問就把他放到熱數(shù)據(jù)區(qū)域的頭部辽狈,之后可能也并不會(huì)再次對(duì)他進(jìn)行訪問慈参,這讓也不符合我們的要求。
MySQL設(shè)定了一個(gè)規(guī)則刮萌,在 innodb_old_blocks_time 參數(shù)中驮配,默認(rèn)值為1000,也就是1000毫秒着茸。
意味著壮锻,只有把數(shù)據(jù)頁(yè)加載進(jìn)緩存里,在經(jīng)過1s之后再次對(duì)此緩存頁(yè)進(jìn)行訪問才會(huì)將緩存頁(yè)放到LRU鏈表熱數(shù)據(jù)區(qū)域的頭部涮阔。
為什么是1秒猜绣?
因?yàn)橥ㄟ^預(yù)讀機(jī)制和全表掃描加載進(jìn)來的數(shù)據(jù)頁(yè)通常是1秒內(nèi)就加載了很多然后對(duì)他們?cè)L問一下,這些都是1秒內(nèi)完成敬特,他們會(huì)存放在冷數(shù)據(jù)區(qū)域等待刷盤清空掰邢,基本上不太會(huì)有機(jī)會(huì)放入到熱數(shù)據(jù)區(qū)域,除非在1秒后還有人訪問伟阔,說明后續(xù)可能還會(huì)有人訪問辣之,才會(huì)放入熱數(shù)據(jù)區(qū)域的頭部。
3) 淘汰緩存頁(yè)
看到這里皱炉,當(dāng)buffer pool中緩存頁(yè)不夠的時(shí)候怀估,要淘汰緩存頁(yè),將緩存頁(yè)刷盤清空是合搅,冷數(shù)據(jù)區(qū)域尾部的緩存頁(yè)無疑是最好的淘汰選擇奏夫。
4) 熱數(shù)據(jù)區(qū)域優(yōu)化
熱數(shù)據(jù)區(qū)域中的緩存頁(yè)會(huì)被頻繁的訪問怕篷,每次都將被訪問的緩存頁(yè)放到LRU鏈表的頭部,這樣對(duì)性能并不友好酗昼。
所以對(duì)熱數(shù)據(jù)區(qū)域的訪問規(guī)則做了優(yōu)化廊谓,只有對(duì)熱數(shù)據(jù)區(qū)域的后3/4的緩存頁(yè)被訪問時(shí),才會(huì)將緩存頁(yè)放到頭部麻削,即是說蒸痹,前1/4的緩存頁(yè)即使被訪問,也不會(huì)移動(dòng)呛哟。
6. 刷盤時(shí)機(jī)
對(duì)于LRU鏈表的緩存頁(yè)刷盤是否需要等待沒有空閑的緩存頁(yè)的時(shí)候才進(jìn)行刷盤叠荠?
緩存頁(yè)刷盤的幾個(gè)時(shí)機(jī):
- 定時(shí)線程執(zhí)行定時(shí)任務(wù),每隔一段時(shí)間扫责,將冷數(shù)據(jù)區(qū)域尾部的一些緩存頁(yè)刷入磁盤榛鼎,清空緩存頁(yè),從LRU鏈表中移除鳖孤,加入到free鏈表當(dāng)中者娱。
- flush鏈表存記錄的是被修改過的緩存頁(yè),也會(huì)被線程隨機(jī)刷盤清空苏揣,從flush鏈表和lru鏈表中移除黄鳍,加入到free鏈表中。
- buffer pool中已經(jīng)沒有空閑的緩存頁(yè)平匈,把冷數(shù)據(jù)區(qū)域尾部的緩存頁(yè)刷盤清空框沟,騰出空閑的緩存頁(yè)給將要加載進(jìn)來的緩存頁(yè)。
所以增炭,在進(jìn)行CRUD的時(shí)候忍燥,形成了一個(gè)動(dòng)態(tài)運(yùn)行的效果。
在不停加載數(shù)據(jù)頁(yè)的時(shí)候隙姿,free鏈表中的緩存頁(yè)會(huì)不斷減少灾前,flush鏈表中的緩存頁(yè)也會(huì)不斷的增加,lru鏈表中的緩存頁(yè)也會(huì)不斷地添加和移動(dòng)孟辑。
另外一邊哎甲,后臺(tái)線程也會(huì)不斷的把lru鏈表中的冷數(shù)據(jù)區(qū)域的尾部緩存頁(yè)以及flush鏈表中的緩存頁(yè),刷入磁盤中來清空緩存頁(yè)饲嗽。lru鏈表和flush鏈表中緩存頁(yè)在不斷減少炭玫,free鏈表中緩存頁(yè)在不斷增加。