wordpress 文章結(jié)尾杭州seo網(wǎng)站排名優(yōu)化
Buffer Pool
本文參考開(kāi)源項(xiàng)目:小林coding在線文檔;
01-緩沖池概述
? 在MySQL查詢數(shù)據(jù)的時(shí)候,是通過(guò)存儲(chǔ)引擎去磁盤做IO來(lái)獲取數(shù)據(jù)庫(kù)中的數(shù)據(jù),這樣每次查詢一條數(shù)據(jù)都要去做一次或者多次磁盤的IO,無(wú)疑是非常慢的。而緩沖池就能非常好的解決這個(gè)問(wèn)題。
? 當(dāng)數(shù)據(jù)從磁盤中取出后,緩存內(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ù),否則再去磁盤中讀取。
- 當(dāng)修改數(shù)據(jù)時(shí),首先是修改 Buffer Pool 中數(shù)據(jù)所在的頁(yè),然后將其頁(yè)設(shè)置為臟頁(yè),最后由后臺(tái)線程將臟頁(yè)寫(xiě)入到磁盤。
02-緩沖池存儲(chǔ)的內(nèi)容
2.1-數(shù)據(jù)頁(yè)在緩沖池中的存儲(chǔ)
? InnoDB 會(huì)把存儲(chǔ)的數(shù)據(jù)劃分為若干個(gè)「頁(yè)」,以頁(yè)作為磁盤和內(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ì)有磁盤上的頁(yè)被緩存到 Buffer Pool 中。
Buffer Pool 除了緩存「索引頁(yè)」和「數(shù)據(jù)頁(yè)」,還包括了 undo 頁(yè),插入緩存、自適應(yīng)哈希索引、鎖信息等等。
接下來(lái)我們討論一下數(shù)據(jù)在緩沖池中是如何存儲(chǔ)以及處理的:
1、既然我們要在緩沖池里存儲(chǔ)數(shù)據(jù)頁(yè),那么數(shù)據(jù)頁(yè)是怎樣存儲(chǔ)的呢?
? 在MySQL啟動(dòng)的時(shí)候,會(huì)申請(qǐng)一段連續(xù)的內(nèi)存空間,緩沖池里有著許多的緩存頁(yè),而每個(gè)緩存頁(yè)有唯一對(duì)應(yīng)一個(gè)控制塊,實(shí)際的存儲(chǔ)情況如下圖所示:
2、為什么上圖會(huì)有空白的地方?
? 上圖中控制塊和緩存頁(yè)之間灰色部分稱為碎片空間。每一個(gè)控制塊都對(duì)應(yīng)一個(gè)緩存頁(yè),那在分配足夠多的控制塊和緩存頁(yè)后,可能剩余的那點(diǎn)兒空間不夠一對(duì)控制塊和緩存頁(yè)的大小,自然就用不到嘍,這個(gè)用不到的那點(diǎn)兒內(nèi)存空間就被稱為碎片了。
? 當(dāng)然,如果你把 Buffer Pool 的大小設(shè)置的剛剛好的話,也可能不會(huì)產(chǎn)生碎片。
2.2-緩沖池?cái)?shù)據(jù)頁(yè)的管理
2.2.1-Free鏈表
? 當(dāng)我們的MySQL運(yùn)行了一段時(shí)間后,緩沖池中的頁(yè)有空閑的也有被使用的,當(dāng)讀取緩沖池中沒(méi)有的數(shù)據(jù)時(shí),我們要從磁盤去讀取,磁盤讀取之后,需要存到緩沖池。
? 但是此時(shí)我們讀取到的數(shù)據(jù)應(yīng)該放到哪個(gè)頁(yè)中呢?當(dāng)然是得放在空閑頁(yè)中,那么我們應(yīng)該如何找到空閑頁(yè)呢,在MySQL緩沖池中,MySQL建立了一個(gè)Free鏈表,用來(lái)管理空閑的緩存頁(yè),當(dāng)我們從磁盤新讀到的數(shù)據(jù),Free鏈表如圖所示。
2.2.2-Flush鏈表
? 當(dāng)對(duì)MySQL的數(shù)據(jù)進(jìn)行修改操作后,并不需要每次都將緩沖池中的頁(yè)寫(xiě)入磁盤,因?yàn)檫@樣效率是比較低的,當(dāng)緩存頁(yè)中的數(shù)據(jù)發(fā)生改變后,MySQL會(huì)將該頁(yè)標(biāo)識(shí)為臟頁(yè)。
? 與空閑頁(yè)相同,MySQL也有一個(gè)Flush鏈表,記錄了緩沖池中所有的臟頁(yè),Flush鏈表中的元素都是臟頁(yè),這樣將臟頁(yè)寫(xiě)入磁盤中就不用再去遍歷所有的緩存頁(yè)查看是否是臟頁(yè)了,直接將Flush鏈表中的所有對(duì)應(yīng)的緩存頁(yè)寫(xiě)入磁盤就行,Flush鏈表如下圖所示:
2.2.4-LRU鏈表
? MySQL為了提高緩沖池的命中率,對(duì)于一些頻繁使用的數(shù)據(jù)需要將其留在緩沖池中,在MySQL中使用了LRU(最近最少使用算法),該算法會(huì)淘汰最近最少使用的頁(yè),在MySQL緩沖池中有一個(gè)LRU鏈表。
下圖我們可以看到LRU鏈表有一個(gè)頭指針和尾指針,一個(gè)簡(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里時(shí),需要先把頁(yè)放在LRU鏈表的頭部,還要將尾部的頁(yè)淘汰掉
假設(shè)現(xiàn)在有一個(gè)LRU鏈表,長(zhǎng)度為5,目前有1、2、3、4、5五個(gè)頁(yè)位于LRU鏈表中,也就是說(shuō)有五個(gè)位于緩沖池中,如圖所示:
現(xiàn)在假設(shè)我們要訪問(wèn)頁(yè)3,那么我們就會(huì)將頁(yè)3移動(dòng)到head的位置,如圖下圖所示:
現(xiàn)在假設(shè)我們要訪問(wèn)頁(yè)8,但是頁(yè)8位于磁盤中,所以此時(shí)我們要將頁(yè)8加入到LRU鏈表中,因此我們會(huì)淘汰掉末尾的頁(yè)5,如圖所示:
2.2.5-預(yù)讀失效
? 對(duì)于如上所示的LRU算法會(huì)有一個(gè)預(yù)讀失效的問(wèn)題,我們先來(lái)解釋一下預(yù)讀失效是什么。
? 在MySQL加載數(shù)據(jù)頁(yè)到緩沖池中時(shí),由于空間局部性【訪問(wèn)謀個(gè)數(shù)據(jù)后,接下來(lái)很可能會(huì)訪問(wèn)其相鄰的數(shù)據(jù)】,加載磁盤中的頁(yè)到內(nèi)存時(shí),會(huì)將相鄰存儲(chǔ)的頁(yè)也加載到內(nèi)存中,但是如果提前加載的數(shù)據(jù)接下來(lái)不會(huì)被訪問(wèn),這個(gè)就叫做預(yù)讀失效。
? 預(yù)讀失效導(dǎo)致的問(wèn)題:
? 如果使用上述的LRU算法,那么就會(huì)導(dǎo)致一個(gè)問(wèn)題,由于預(yù)讀失效,加載了不會(huì)被訪問(wèn)的頁(yè)放在了緩沖池中,由于加載了新的頁(yè),所以會(huì)要淘汰緩沖池中存在的頁(yè),那么就會(huì)導(dǎo)致緩沖池中可能會(huì)被頻繁訪問(wèn)的頁(yè)被淘汰了出去,這樣就會(huì)降低緩沖池命中率。
? MySQL對(duì)于預(yù)讀問(wèn)題的解決方案是將LRU鏈表劃分成young
區(qū)域和old
區(qū)域,young區(qū)在LRU鏈表的前半部分,而old區(qū)域在LRU鏈表的后半部分。如下圖所示。
? 關(guān)于young區(qū)域和old區(qū)域在LRU鏈表中的占比也可以通過(guò)參數(shù)設(shè)置,可以通過(guò)innodb_old_blocks_pct
參數(shù)進(jìn)行設(shè)置,默認(rèn)值是37,代表在緩沖池中,young區(qū)域和old區(qū)域占比是63:37。
? 解決預(yù)讀失效產(chǎn)生的問(wèn)題,當(dāng)發(fā)生預(yù)讀的時(shí)候,MySQL不會(huì)將預(yù)讀的數(shù)據(jù)放到y(tǒng)oung區(qū)域,而是放在old區(qū)域的head部分,只有當(dāng)預(yù)讀的數(shù)據(jù)被訪問(wèn)的時(shí)候,才會(huì)被放在young區(qū)域的head部分,
2.2.6-Buffer Pool污染
? 上述使用LRU鏈表分young區(qū)和old區(qū)雖然能夠解決預(yù)讀失效導(dǎo)致的命中率下降問(wèn)題,但是還存在Buffer Pool污染問(wèn)題,我們先來(lái)介紹一下什么是buffer pool污染。
? 當(dāng)一個(gè)SQL語(yǔ)句掃描了大量的數(shù)據(jù)的時(shí)候,在緩沖池空間比較有限的情況下,可能會(huì)將緩沖池中所有的熱點(diǎn)數(shù)據(jù)都替換出去,等這些熱點(diǎn)數(shù)據(jù)被再次訪問(wèn)的時(shí)候,由于緩存沒(méi)有命中,就會(huì)產(chǎn)生大量磁盤IO,導(dǎo)致MySQL性能急劇下降,這種情況就叫Buffer Pool污染。
? 關(guān)于Buffer Pool污染的問(wèn)題,MySQL是這樣處理的,進(jìn)入young區(qū)域條件增加了一個(gè)停留在old區(qū)域的時(shí)間判斷。具體的處理是這樣的,當(dāng)訪問(wèn)第一次old區(qū)域的某個(gè)緩存頁(yè)時(shí),在其對(duì)應(yīng)的控制塊中記錄當(dāng)前訪問(wèn)的時(shí)間:
- 如果后續(xù)訪問(wèn)時(shí)間與第一次訪問(wèn)的時(shí)間在某個(gè)時(shí)間段內(nèi),那么這個(gè)緩存頁(yè)不會(huì)從old區(qū)移到y(tǒng)oung區(qū)的頭部
- 如果后續(xù)訪問(wèn)時(shí)間與第一次訪問(wèn)的時(shí)間不在某個(gè)時(shí)間段內(nèi),那么就會(huì)將這個(gè)緩存頁(yè)從old區(qū)域移動(dòng)到y(tǒng)oung區(qū)的頭部
這個(gè)時(shí)間間隔可以通過(guò)innodb_old_blocks_time
控制,默認(rèn)的時(shí)間是1000ms。
? 如果在默認(rèn)的情況下,只有同時(shí)滿足一次以上的訪問(wèn)
以及在old區(qū)停留超過(guò)1s
兩個(gè)條件,才會(huì)被從old區(qū)移動(dòng)到y(tǒng)oung區(qū)的頭部,這樣就解決了Buffer Pool污染的問(wèn)題。
?