答應(yīng)我,這次要搞懂 Buffer Pool
今天聊 MySQL 的 Buffer Pool,發(fā)車!
為什么要有 Buffer Pool?
雖然說(shuō) MySQL 的數(shù)據(jù)是存儲(chǔ)在磁盤(pán)里的,但是也不能每次都從磁盤(pán)里面讀取數(shù)據(jù),這樣性能是極差的。
要想提升查詢性能,加個(gè)緩存就行了嘛。所以,當(dāng)數(shù)據(jù)從磁盤(pán)中取出后,緩存內(nèi)存中,下次查詢同樣的數(shù)據(jù)的時(shí)候,直接從內(nèi)存中讀取。
為此,Innodb 存儲(chǔ)引擎設(shè)計(jì)了一個(gè)緩沖池(Buffer Pool),來(lái)提高數(shù)據(jù)庫(kù)的讀寫(xiě)性能。
有了緩沖池后:
- 當(dāng)讀取數(shù)據(jù)時(shí),如果數(shù)據(jù)存在于 Buffer Pool 中,客戶端就會(huì)直接讀取 Buffer Pool 中的數(shù)據(jù),否則再去磁盤(pán)中讀取。
- 當(dāng)修改數(shù)據(jù)時(shí),首先是修改 Buffer Pool 中數(shù)據(jù)所在的頁(yè),然后將其頁(yè)設(shè)置為臟頁(yè),最后由后臺(tái)線程將臟頁(yè)寫(xiě)入到磁盤(pán)。
Buffer Pool 有多大?
Buffer Pool 是在 MySQL 啟動(dòng)的時(shí)候,向操作系統(tǒng)申請(qǐng)的一片連續(xù)的內(nèi)存空間,默認(rèn)配置下 Buffer Pool 只有 128MB 。
可以通過(guò)調(diào)整 innodb_buffer_pool_size 參數(shù)來(lái)設(shè)置 Buffer Pool 的大小,一般建議設(shè)置成可用物理內(nèi)存的 60%~80%。
Buffer Pool 緩存什么?
InnoDB 會(huì)把存儲(chǔ)的數(shù)據(jù)劃分為若干個(gè)「頁(yè)」,以頁(yè)作為磁盤(pán)和內(nèi)存交互的基本單位,一個(gè)頁(yè)的默認(rèn)大小為 16KB。因此,Buffer Pool 同樣需要按「頁(yè)」來(lái)劃分。
在 MySQL 啟動(dòng)的時(shí)候,InnoDB 會(huì)為 Buffer Pool 申請(qǐng)一片連續(xù)的內(nèi)存空間,然后按照默認(rèn)的16KB的大小劃分出一個(gè)個(gè)的頁(yè), Buffer Pool 中的頁(yè)就叫做緩存頁(yè)。此時(shí)這些緩存頁(yè)都是空閑的,之后隨著程序的運(yùn)行,才會(huì)有磁盤(pán)上的頁(yè)被緩存到 Buffer Pool 中。
所以,MySQL 剛啟動(dòng)的時(shí)候,你會(huì)觀察到使用的虛擬內(nèi)存空間很大,而使用到的物理內(nèi)存空間卻很小,這是因?yàn)橹挥羞@些虛擬內(nèi)存被訪問(wèn)后,操作系統(tǒng)才會(huì)觸發(fā)缺頁(yè)中斷,接著將虛擬地址和物理地址建立映射關(guān)系。
Buffer Pool 除了緩存「索引頁(yè)」和「數(shù)據(jù)頁(yè)」,還包括了 undo 頁(yè),插入緩存、自適應(yīng)哈希索引、鎖信息等等。
為了更好的管理這些在 Buffer Pool 中的緩存頁(yè),InnoDB 為每一個(gè)緩存頁(yè)都創(chuàng)建了一個(gè)控制塊,控制塊信息包括「緩存頁(yè)的表空間、頁(yè)號(hào)、緩存頁(yè)地址、鏈表節(jié)點(diǎn)」等等。
控制塊也是占有內(nèi)存空間的,它是放在 Buffer Pool 的最前面,接著才是緩存頁(yè),如下圖:
上圖中控制塊和緩存頁(yè)之間灰色部分稱為碎片空間。
為什么會(huì)有碎片空間呢?
你想想啊,每一個(gè)控制塊都對(duì)應(yīng)一個(gè)緩存頁(yè),那在分配足夠多的控制塊和緩存頁(yè)后,可能剩余的那點(diǎn)兒空間不夠一對(duì)控制塊和緩存頁(yè)的大小,自然就用不到嘍,這個(gè)用不到的那點(diǎn)兒內(nèi)存空間就被稱為碎片了。
當(dāng)然,如果你把 Buffer Pool 的大小設(shè)置的剛剛好的話,也可能不會(huì)產(chǎn)生碎片。
查詢一條記錄,就只需要緩沖一條記錄嗎?
不是的。
當(dāng)我們查詢一條記錄時(shí),InnoDB 是會(huì)把整個(gè)頁(yè)的數(shù)據(jù)加載到 Buffer Pool 中,因?yàn)?,通過(guò)索引只能定位到磁盤(pán)中的頁(yè),而不能定位到頁(yè)中的一條記錄。將頁(yè)加載到 Buffer Pool 后,再通過(guò)頁(yè)里的頁(yè)目錄去定位到某條具體的記錄。
如何管理 Buffer Pool?
如何管理空閑頁(yè)?
當(dāng)啟動(dòng) Mysql 服務(wù)器的時(shí)候,需要完成對(duì) Buffer Pool 的初始化過(guò)程,即分配 Buffer Pool 的內(nèi)存空間,把它劃分為若干對(duì)控制塊和緩存頁(yè)。
但是此時(shí)并沒(méi)有真正的磁盤(pán)頁(yè)被緩存到 Buffer Pool 中,之后隨著程序的運(yùn)行,會(huì)不斷的有磁盤(pán)上的頁(yè)被緩存到 Buffer Pool 中。
為了標(biāo)記哪些頁(yè)是空閑頁(yè),就使用了鏈表結(jié)構(gòu),將空閑緩存頁(yè)的「控制塊」作為鏈表的節(jié)點(diǎn),一個(gè)一個(gè)串起來(lái),這個(gè)鏈表稱為 Free 鏈表(空閑鏈表)。
Free 鏈表上除了有控制塊,還有一個(gè)頭節(jié)點(diǎn),該頭節(jié)點(diǎn)包含鏈表的頭節(jié)點(diǎn)地址,尾節(jié)點(diǎn)地址,以及當(dāng)前鏈表中節(jié)點(diǎn)的數(shù)量等信息。
Free 鏈表節(jié)點(diǎn)是一個(gè)一個(gè)的控制塊,而每個(gè)控制塊包含著對(duì)應(yīng)緩存頁(yè)的地址,所以相當(dāng)于 Free 鏈表節(jié)點(diǎn)都對(duì)應(yīng)一個(gè)空閑的緩存頁(yè)。
有了 Free 鏈表后,每當(dāng)需要從磁盤(pán)中加載一個(gè)頁(yè)到 Buffer Pool 中時(shí),就從 Free 鏈表中取一個(gè)空閑的緩存頁(yè),并且把該緩存頁(yè)對(duì)應(yīng)的控制塊的信息填上,然后把該緩存頁(yè)對(duì)應(yīng)的控制塊從 Free 鏈表中移除。
如何管理臟頁(yè)?
設(shè)計(jì) Buffer Pool 除了能提高讀性能,還能提高寫(xiě)性能,也就是更新數(shù)據(jù)的時(shí)候,不需要每次都要寫(xiě)入磁盤(pán),而是將 Buffer Pool 對(duì)應(yīng)的緩存頁(yè)標(biāo)記為臟頁(yè),然后再由后臺(tái)線程將臟頁(yè)寫(xiě)入到磁盤(pán)。
那為了能快速知道哪些緩存頁(yè)是臟的,于是就設(shè)計(jì)出 Flush 鏈表,它跟 Free 鏈表類似的,鏈表的節(jié)點(diǎn)也是控制塊,區(qū)別在于 Flush 鏈表的元素都是臟頁(yè)。
有了 Flush 鏈表后,后臺(tái)線程就可以遍歷 Flush 鏈表,將臟頁(yè)寫(xiě)入到磁盤(pán)。
如何提高緩存命中率?
Buffer Pool 的大小是有限的,對(duì)于一些頻繁訪問(wèn)的數(shù)據(jù)我們希望可以一直留在 Buffer Pool 中,而一些很少訪問(wèn)的數(shù)據(jù)希望可以在某些時(shí)機(jī)可以淘汰掉,從而保證 Buffer Pool 不會(huì)因?yàn)闈M了而導(dǎo)致無(wú)法再緩存新的數(shù)據(jù),同時(shí)還能保證常用數(shù)據(jù)留在 Buffer Pool 中。
要實(shí)現(xiàn)這個(gè),最容易想到的就是 LRU(Least recently used)算法。
該算法的思路是,鏈表頭部的節(jié)點(diǎn)是最近使用的,而鏈表末尾的節(jié)點(diǎn)是最久沒(méi)被使用的。那么,當(dāng)空間不夠了,就淘汰最久沒(méi)被使用的節(jié)點(diǎn),從而騰出空間。
簡(jiǎn)單的 LRU 算法的實(shí)現(xiàn)思路是這樣的:
當(dāng)訪問(wèn)的頁(yè)在 Buffer Pool 里,就直接把該頁(yè)對(duì)應(yīng)的 LRU 鏈表節(jié)點(diǎn)移動(dòng)到鏈表的頭部。
當(dāng)訪問(wèn)的頁(yè)不在 Buffer Pool 里,除了要把頁(yè)放入到 LRU 鏈表的頭部,還要淘汰 LRU 鏈表末尾的節(jié)點(diǎn)。
比如下圖,假設(shè) LRU 鏈表長(zhǎng)度為 5,LRU 鏈表從左到右有 1,2,3,4,5 的頁(yè)。
如果訪問(wèn)了 3 號(hào)的頁(yè),因?yàn)?3 號(hào)頁(yè)在 Buffer Pool 里,所以把 3 號(hào)頁(yè)移動(dòng)到頭部即可。
而如果接下來(lái),訪問(wèn)了 8 號(hào)頁(yè),因?yàn)?8 號(hào)頁(yè)不在 Buffer Pool 里,所以需要先淘汰末尾的 5 號(hào)頁(yè),然后再將 8 號(hào)頁(yè)加入到頭部。
到這里我們可以知道,Buffer Pool 里有三種頁(yè)和鏈表來(lái)管理數(shù)據(jù)。
圖中:
- Free Page(空閑頁(yè)),表示此頁(yè)未被使用,位于 Free 鏈表;
- Clean Page(干凈頁(yè)),表示此頁(yè)已被使用,但是頁(yè)面未發(fā)生修改,位于LRU 鏈表。
- Dirty Page(臟頁(yè)),表示此頁(yè)「已被使用」且「已經(jīng)被修改」,其數(shù)據(jù)和磁盤(pán)上的數(shù)據(jù)已經(jīng)不一致。當(dāng)臟頁(yè)上的數(shù)據(jù)寫(xiě)入磁盤(pán)后,內(nèi)存數(shù)據(jù)和磁盤(pán)數(shù)據(jù)一致,那么該頁(yè)就變成了干凈頁(yè)。臟頁(yè)同時(shí)存在于 LRU 鏈表和 Flush 鏈表。
簡(jiǎn)單的 LRU 算法并沒(méi)有被 MySQL 使用,因?yàn)楹?jiǎn)單的 LRU 算法無(wú)法避免下面這兩個(gè)問(wèn)題:
- 預(yù)讀失效;
- Buffer Pool 污染;
什么是預(yù)讀失效?
先來(lái)說(shuō)說(shuō) MySQL 的預(yù)讀機(jī)制。程序是有空間局部性的,靠近當(dāng)前被訪問(wèn)數(shù)據(jù)的數(shù)據(jù),在未來(lái)很大概率會(huì)被訪問(wèn)到。
所以,MySQL 在加載數(shù)據(jù)頁(yè)時(shí),會(huì)提前把它相鄰的數(shù)據(jù)頁(yè)一并加載進(jìn)來(lái),目的是為了減少磁盤(pán) IO。
但是可能這些被提前加載進(jìn)來(lái)的數(shù)據(jù)頁(yè),并沒(méi)有被訪問(wèn),相當(dāng)于這個(gè)預(yù)讀是白做了,這個(gè)就是預(yù)讀失效。
如果使用簡(jiǎn)單的 LRU 算法,就會(huì)把預(yù)讀頁(yè)放到 LRU 鏈表頭部,而當(dāng) Buffer Pool空間不夠的時(shí)候,還需要把末尾的頁(yè)淘汰掉。
如果這些預(yù)讀頁(yè)如果一直不會(huì)被訪問(wèn)到,就會(huì)出現(xiàn)一個(gè)很奇怪的問(wèn)題,不會(huì)被訪問(wèn)的預(yù)讀頁(yè)卻占用了 LRU 鏈表前排的位置,而末尾淘汰的頁(yè),可能是頻繁訪問(wèn)的頁(yè),這樣就大大降低了緩存命中率。
怎么解決預(yù)讀失效而導(dǎo)致緩存命中率降低的問(wèn)題?
我們不能因?yàn)楹ε骂A(yù)讀失效,而將預(yù)讀機(jī)制去掉,大部分情況下,局部性原理還是成立的。
要避免預(yù)讀失效帶來(lái)影響,最好就是讓預(yù)讀的頁(yè)停留在 Buffer Pool 里的時(shí)間要盡可能的短,讓真正被訪問(wèn)的頁(yè)才移動(dòng)到 LRU 鏈表的頭部,從而保證真正被讀取的熱數(shù)據(jù)留在 Buffer Pool 里的時(shí)間盡可能長(zhǎng)。
那到底怎么才能避免呢?
MySQL 是這樣做的,它改進(jìn)了 LRU 算法,將 LRU 劃分了 2 個(gè)區(qū)域:old 區(qū)域 和 young 區(qū)域。
young 區(qū)域在 LRU 鏈表的前半部分,old 區(qū)域則是在后半部分,如下圖:
old 區(qū)域占整個(gè) LRU 鏈表長(zhǎng)度的比例可以通過(guò) innodb_old_blocks_pc 參數(shù)來(lái)設(shè)置,默認(rèn)是 37,代表整個(gè) LRU 鏈表中 young 區(qū)域與 old 區(qū)域比例是 63:37。
劃分這兩個(gè)區(qū)域后,預(yù)讀的頁(yè)就只需要加入到 old 區(qū)域的頭部,當(dāng)頁(yè)被真正訪問(wèn)的時(shí)候,才將頁(yè)插入 young 區(qū)域的頭部。如果預(yù)讀的頁(yè)一直沒(méi)有被訪問(wèn),就會(huì)從 old 區(qū)域移除,這樣就不會(huì)影響 young 區(qū)域中的熱點(diǎn)數(shù)據(jù)。
接下來(lái),給大家舉個(gè)例子。
假設(shè)有一個(gè)長(zhǎng)度為 10 的 LRU 鏈表,其中 young 區(qū)域占比 70 %,old 區(qū)域占比 20 %。
現(xiàn)在有個(gè)編號(hào)為 20 的頁(yè)被預(yù)讀了,這個(gè)頁(yè)只會(huì)被插入到 old 區(qū)域頭部,而 old 區(qū)域末尾的頁(yè)(10號(hào))會(huì)被淘汰掉。
如果 20 號(hào)頁(yè)一直不會(huì)被訪問(wèn),它也沒(méi)有占用到 young 區(qū)域的位置,而且還會(huì)比 young 區(qū)域的數(shù)據(jù)更早被淘汰出去。
如果 20 號(hào)頁(yè)被預(yù)讀后,立刻被訪問(wèn)了,那么就會(huì)將它插入到 young 區(qū)域的頭部,young 區(qū)域末尾的頁(yè)(7號(hào)),會(huì)被擠到 old 區(qū)域,作為 old 區(qū)域的頭部,這個(gè)過(guò)程并不會(huì)有頁(yè)被淘汰。
雖然通過(guò)劃分 old 區(qū)域 和 young 區(qū)域避免了預(yù)讀失效帶來(lái)的影響,但是還有個(gè)問(wèn)題無(wú)法解決,那就是 Buffer Pool 污染的問(wèn)題。
什么是 Buffer Pool 污染?
當(dāng)某一個(gè) SQL 語(yǔ)句掃描了大量的數(shù)據(jù)時(shí),在 Buffer Pool 空間比較有限的情況下,可能會(huì)將 Buffer Pool 里的所有頁(yè)都替換出去,導(dǎo)致大量熱數(shù)據(jù)被淘汰了,等這些熱數(shù)據(jù)又被再次訪問(wèn)的時(shí)候,由于緩存未命中,就會(huì)產(chǎn)生大量的磁盤(pán) IO,MySQL 性能就會(huì)急劇下降,這個(gè)過(guò)程被稱為 Buffer Pool 污染。
注意, Buffer Pool 污染并不只是查詢語(yǔ)句查詢出了大量的數(shù)據(jù)才出現(xiàn)的問(wèn)題,即使查詢出來(lái)的結(jié)果集很小,也會(huì)造成 Buffer Pool 污染。
比如,在一個(gè)數(shù)據(jù)量非常大的表,執(zhí)行了這條語(yǔ)句:
select * from t_user where name like "%xiaolin%";
可能這個(gè)查詢出來(lái)的結(jié)果就幾條記錄,但是由于這條語(yǔ)句會(huì)發(fā)生索引失效,所以這個(gè)查詢過(guò)程是全表掃描的,接著會(huì)發(fā)生如下的過(guò)程:
- 從磁盤(pán)讀到的頁(yè)加入到 LRU 鏈表的 old 區(qū)域頭部;
- 當(dāng)從頁(yè)里讀取行記錄時(shí),也就是頁(yè)被訪問(wèn)的時(shí)候,就要將該頁(yè)放到 young 區(qū)域頭部;
- 接下來(lái)拿行記錄的 name 字段和字符串 xiaolin 進(jìn)行模糊匹配,如果符合條件,就加入到結(jié)果集里;
- 如此往復(fù),直到掃描完表中的所有記錄。
經(jīng)過(guò)這一番折騰,原本 young 區(qū)域的熱點(diǎn)數(shù)據(jù)都會(huì)被替換掉。
舉個(gè)例子,假設(shè)需要批量掃描:21,22,23,24,25 這五個(gè)頁(yè),這些頁(yè)都會(huì)被逐一訪問(wèn)(讀取頁(yè)里的記錄)。
在批量訪問(wèn)這些數(shù)據(jù)的時(shí)候,會(huì)被逐一插入到 young 區(qū)域頭部。
可以看到,原本在 young 區(qū)域的熱點(diǎn)數(shù)據(jù) 6 和 7 號(hào)頁(yè)都被淘汰了,這就是 Buffer Pool 污染的問(wèn)題。
怎么解決出現(xiàn) Buffer Pool 污染而導(dǎo)致緩存命中率下降的問(wèn)題?
像前面這種全表掃描的查詢,很多緩沖頁(yè)其實(shí)只會(huì)被訪問(wèn)一次,但是它卻只因?yàn)楸辉L問(wèn)了一次而進(jìn)入到 young 區(qū)域,從而導(dǎo)致熱點(diǎn)數(shù)據(jù)被替換了。
LRU 鏈表中 young 區(qū)域就是熱點(diǎn)數(shù)據(jù),只要我們提高進(jìn)入到 young 區(qū)域的門(mén)檻,就能有效地保證 young 區(qū)域里的熱點(diǎn)數(shù)據(jù)不會(huì)被替換掉。
MySQL 是這樣做的,進(jìn)入到 young 區(qū)域條件增加了一個(gè)停留在 old 區(qū)域的時(shí)間判斷。
具體是這樣做的,在對(duì)某個(gè)處在 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)到 young 區(qū)域的頭部;
- 如果后續(xù)的訪問(wèn)時(shí)間與第一次訪問(wèn)的時(shí)間不在某個(gè)時(shí)間間隔內(nèi),那么該緩存頁(yè)移動(dòng)到 young 區(qū)域的頭部;
這個(gè)間隔時(shí)間是由 innodb_old_blocks_time 控制的,默認(rèn)是 1000 ms。
也就說(shuō),只有同時(shí)滿足「被訪問(wèn)」與「在 old 區(qū)域停留時(shí)間超過(guò) 1 秒」兩個(gè)條件,才會(huì)被插入到 young 區(qū)域頭部,這樣就解決了 Buffer Pool 污染的問(wèn)題 。
另外,MySQL 針對(duì) young 區(qū)域其實(shí)做了一個(gè)優(yōu)化,為了防止 young 區(qū)域節(jié)點(diǎn)頻繁移動(dòng)到頭部。young 區(qū)域前面 1/4 被訪問(wèn)不會(huì)移動(dòng)到鏈表頭部,只有后面的 3/4被訪問(wèn)了才會(huì)。
臟頁(yè)什么時(shí)候會(huì)被刷入磁盤(pán)?
引入了 Buffer Pool 后,當(dāng)修改數(shù)據(jù)時(shí),首先是修改 Buffer Pool 中數(shù)據(jù)所在的頁(yè),然后將其頁(yè)設(shè)置為臟頁(yè),但是磁盤(pán)中還是原數(shù)據(jù)。
因此,臟頁(yè)需要被刷入磁盤(pán),保證緩存和磁盤(pán)數(shù)據(jù)一致,但是若每次修改數(shù)據(jù)都刷入磁盤(pán),則性能會(huì)很差,因此一般都會(huì)在一定時(shí)機(jī)進(jìn)行批量刷盤(pán)。
可能大家擔(dān)心,如果在臟頁(yè)還沒(méi)有來(lái)得及刷入到磁盤(pán)時(shí),MySQL 宕機(jī)了,不就丟失數(shù)據(jù)了嗎?
這個(gè)不用擔(dān)心,InnoDB 的更新操作采用的是 Write Ahead Log 策略,即先寫(xiě)日志,再寫(xiě)入磁盤(pán),通過(guò) redo log 日志讓 MySQL 擁有了崩潰恢復(fù)能力。
下面幾種情況會(huì)觸發(fā)臟頁(yè)的刷新:
- 當(dāng) redo log 日志滿了的情況下,會(huì)主動(dòng)觸發(fā)臟頁(yè)刷新到磁盤(pán);
- Buffer Pool 空間不足時(shí),需要將一部分?jǐn)?shù)據(jù)頁(yè)淘汰掉,如果淘汰的是臟頁(yè),需要先將臟頁(yè)同步到磁盤(pán);
- MySQL 認(rèn)為空閑時(shí),后臺(tái)線程回定期將適量的臟頁(yè)刷入到磁盤(pán);
- MySQL 正常關(guān)閉之前,會(huì)把所有的臟頁(yè)刷入到磁盤(pán);
在我們開(kāi)啟了慢 SQL 監(jiān)控后,如果你發(fā)現(xiàn)「偶爾」會(huì)出現(xiàn)一些用時(shí)稍長(zhǎng)的 SQL,這可能是因?yàn)榕K頁(yè)在刷新到磁盤(pán)時(shí)可能會(huì)給數(shù)據(jù)庫(kù)帶來(lái)性能開(kāi)銷,導(dǎo)致數(shù)據(jù)庫(kù)操作抖動(dòng)。
如果間斷出現(xiàn)這種現(xiàn)象,就需要調(diào)大 Buffer Pool 空間或 redo log 日志的大小。
總結(jié)
Innodb 存儲(chǔ)引擎設(shè)計(jì)了一個(gè)緩沖池(Buffer Pool),來(lái)提高數(shù)據(jù)庫(kù)的讀寫(xiě)性能。
Buffer Pool 以頁(yè)為單位緩沖數(shù)據(jù),可以通過(guò) innodb_buffer_pool_size 參數(shù)調(diào)整緩沖池的大小,默認(rèn)是 128 M。
Innodb 通過(guò)三種鏈表來(lái)管理緩頁(yè):
- Free List (空閑頁(yè)鏈表),管理空閑頁(yè);
- Flush List (臟頁(yè)鏈表),管理臟頁(yè);
- LRU List,管理臟頁(yè)+干凈頁(yè),將最近且經(jīng)常查詢的數(shù)據(jù)緩存在其中,而不常查詢的數(shù)據(jù)就淘汰出去。;
InnoDB 對(duì) LRU 做了一些優(yōu)化,我們熟悉的 LRU 算法通常是將最近查詢的數(shù)據(jù)放到 LRU 鏈表的頭部,而 InnoDB 做 2 點(diǎn)優(yōu)化:
- 將 LRU 鏈表 分為young 和 old 兩個(gè)區(qū)域,加入緩沖池的頁(yè),優(yōu)先插入 old 區(qū)域;頁(yè)被訪問(wèn)時(shí),才進(jìn)入 young 區(qū)域,目的是為了解決預(yù)讀失效的問(wèn)題。
- 當(dāng)「頁(yè)被訪問(wèn)」且「 old 區(qū)域停留時(shí)間超過(guò) innodb_old_blocks_time 閾值(默認(rèn)為1秒)」時(shí),才會(huì)將頁(yè)插入到 young 區(qū)域,否則還是插入到 old 區(qū)域,目的是為了解決批量數(shù)據(jù)訪問(wèn),大量熱數(shù)據(jù)淘汰的問(wèn)題。
可以通過(guò)調(diào)整 innodb_old_blocks_pc 參數(shù),設(shè)置 young 區(qū)域和 old 區(qū)域比例。
在開(kāi)啟了慢 SQL 監(jiān)控后,如果你發(fā)現(xiàn)「偶爾」會(huì)出現(xiàn)一些用時(shí)稍長(zhǎng)的 SQL,這可因?yàn)榕K頁(yè)在刷新到磁盤(pán)時(shí)導(dǎo)致數(shù)據(jù)庫(kù)性能抖動(dòng)。如果在較短的時(shí)間頻繁出現(xiàn)這種現(xiàn)象,就需要調(diào)大 Buffer Pool 空間或 redo log 日志的大小,從而減少臟頁(yè)刷入磁盤(pán)的頻率。