1 概述
(本文屬于閱讀筆記术荤,基本上翻譯 MySQL reference 15.5.1 Buffer Pool )
在MySQL reference介紹InnoDB內(nèi)存中的數(shù)據(jù)結(jié)構(gòu)中介紹了緩沖池的改進(jìn)LRU算法沈善,MySQL使用列表來實(shí)現(xiàn)緩沖池蹦疑。當(dāng)緩沖池空間不夠時(shí)张漂,緩沖池中最近最少被使用的頁(yè)會(huì)被淘汰罗晕,新的頁(yè)會(huì)被加入到列表的中間位置误褪。采用中間插入(midpoint悉患,注意這里雖然說是中間,但是不一定是嚴(yán)格的中間位置挡鞍,比如默認(rèn)配置下酌泰,該位置從表頭計(jì)算為列表5/8的位置)的策略使得列表被分成了如下兩個(gè)子列表:
- 在表頭保存的是最近被訪問的新("young")頁(yè)子表。
- 在表尾是最近沒有被訪問的舊頁(yè)子表匕累。
具體的可以參考官網(wǎng)的圖如下:
2 實(shí)現(xiàn)
MySQL改進(jìn)的LRU算法將那些被頻繁查詢的頁(yè)放在表頭部的新頁(yè)子表中,表尾的舊頁(yè)子表則放那些較少使用的頁(yè)默伍,這些頁(yè)也是淘汰的候選頁(yè)欢嘿。
默認(rèn)配置下,算法操作如下:
3/8的列表劃分為尾部的舊頁(yè)子表也糊,存放那些可以淘汰的舊頁(yè)炼蹦。
列表中點(diǎn)(midpoint,注意這里雖然說是中點(diǎn)狸剃,但是不一定是嚴(yán)格的中間位置掐隐,比如默認(rèn)配置下,該位置從表頭計(jì)算為列表5/8的位置)是新頁(yè)子表和舊頁(yè)子表的邊界钞馁。
當(dāng) InnoDB從磁盤讀一頁(yè)數(shù)據(jù)并放入緩沖池中時(shí)虑省,它會(huì)將此頁(yè)插入到列表的中間位置(也就是舊頁(yè)子表的頭部)。發(fā)生讀頁(yè)一般是因?yàn)橛脩舨樵償?shù)據(jù)僧凰,或者InnoDB自動(dòng)觸發(fā)的read-ahead操作探颈。
讀取舊頁(yè)子表中的數(shù)據(jù)會(huì)讓該頁(yè)變新(年輕,young)训措,并將其移動(dòng)到緩沖池的頭部(也就是新頁(yè)子表的頭部)伪节。如果是因?yàn)橛脩舨樵冏x造成該頁(yè)被讀取光羞,則該頁(yè)會(huì)立即被標(biāo)識(shí)為年輕,并直接插入到列表頭部怀大。如果該頁(yè)因?yàn)閞ead-ahead被讀取纱兑,則首次讀取該頁(yè)并放入緩沖池時(shí)不會(huì)將該頁(yè)放入新頁(yè)列表頭部,而是放入列表中點(diǎn)化借,需要再次讀取才能使該頁(yè)被標(biāo)識(shí)為年輕狀態(tài)潜慎。(該頁(yè)可能一直沒有被標(biāo)識(shí)為年輕狀態(tài)直到被淘汰)。
MySQL通過參數(shù)innodb_old_blocks_pct
來控制舊頁(yè)子表占整個(gè)緩沖池列表的比例屏鳍,默認(rèn)為37勘纯,也就是上面說的3/8。
默認(rèn)的緩沖中的頁(yè)在第一次被讀取時(shí)(也就是命中緩存)會(huì)被移動(dòng)到新頁(yè)子表頭部钓瞭,意味著其會(huì)長(zhǎng)期待在緩沖池中不會(huì)被淘汰驳遵。這樣就會(huì)存在一個(gè)問題,一次表掃描(比如使用mysqldump或者沒有條件的select查詢)可能會(huì)將大量數(shù)據(jù)放入緩存中山涡,并淘汰相應(yīng)數(shù)量的舊數(shù)據(jù)堤结,但是可能這些數(shù)據(jù)只使用一次,后面不再使用鸭丛;同樣地竞穷,因?yàn)镸ySQL自動(dòng)觸發(fā)的read-ahead也會(huì)在下一次訪問該頁(yè)時(shí)被放入新頁(yè)子表頭部。這些情形會(huì)將本應(yīng)會(huì)被頻繁使用的頁(yè)移動(dòng)到舊頁(yè)子表中鳞溉。
所以MySQL采用如下方式避免上面的問題瘾带,新讀取的頁(yè)會(huì)放入緩沖池中點(diǎn),也即默認(rèn)情況下所有的新讀取的頁(yè)都會(huì)被插入到尾部開始的3/8位置處熟菲。在后面的第一次命中(被訪問時(shí))的頁(yè)會(huì)被移動(dòng)到列表的頭部看政。因此,那些讀入緩存但是后面從來不會(huì)被訪問的頁(yè)也從不會(huì)被放入列表的頭部抄罕,也就會(huì)在后面被從緩沖池淘汰允蚣。
read-ahead、或者表呆贿、索引掃描都會(huì)造成類似的緩沖池?cái)_動(dòng)嚷兔。在這些情景下,頁(yè)通常會(huì)被讀茸鋈搿(命中)若干次冒晰,然后從此不再訪問。為此MySQL提供了配置參數(shù)innodb_old_blocks_time
用來指定該頁(yè)在放入緩沖池后第一次讀之后一定時(shí)間內(nèi)(時(shí)間窗口竟块,單位毫秒,milliseconds)讀取不會(huì)被標(biāo)識(shí)為年輕翩剪,也就是不會(huì)被移動(dòng)到列表頭部。參數(shù)innodb_old_blocks_time
的默認(rèn)值是1000彩郊,增大這個(gè)參數(shù)將會(huì)造成更多的頁(yè)會(huì)更快的從緩沖池中被淘汰前弯。