緩衝池裡的祕密,你不好奇嗎?

*IT界農民工*發表於2020-12-29

Mysql 中資料是要落盤的,這點大家都知道。讀寫磁碟速度是很慢的,尤其和記憶體比起來更是沒的說。但是,我們平時在執行 SQL 時,無論寫操作還是讀操作都能很快得到結果,並沒有預想中的那麼慢。

可能你會說我有索引啊,有索引當然快了。但是鐵子,索引檔案也是儲存在磁碟上的,查詢過程會產生磁碟 I/O。如果同時對某行資料進行多次操作,那豈不是要重複產生很多次磁碟 IO 嗎?

可能你想到了,那我把資料存在記憶體裡不就可以了嗎?記憶體速度比磁碟快,這準沒毛病。沒錯,那該怎麼存呢? 這就是我們今天所要講的主題——緩衝池(buffer pool)。

各位看官,請跟我來~

圖注:思維導圖

初識緩衝池

上邊我們提到過了,執行 SQL 對某一行進行操作時,總不能每次都直接進行磁碟操作吧。好歹有個緩衝地帶,不然每次都深入老巢這誰受得了。

這不緩衝池就應運而生了,簡單來說就是一塊記憶體區域。它存在的原因之一是為了避免每次都去訪問磁碟,把最常訪問的資料放在快取裡,提高資料的訪問速度。

瞭解了它的作用,接下來讓我們先來看下緩衝池在整個 Mysql 架構裡處於什麼樣的地方,有一個巨集觀的認識。

我們再來看看它的內部組成部分。在緩衝池中,除資料頁和索引頁外還有多種型別:

緩衝池的應用

緩衝池你也瞭解了,可能此時你最關注的是它在 SQL 執行時起了一個什麼樣的作用。上篇文章中我們簡單的提到過一條 SQL 語句的執行過程,但並未涉及到緩衝池相關的問題。這期我們仍是以一條 SQL 來作為切入點。

當一條 SQL 執行的時候,如果是讀操作,要查詢的資料所在的資料頁在記憶體中時,則將結果返回。否則會把對應的資料頁載入到記憶體中,然後再返回結果。

同樣對於寫操作來說。如果要修改的行所在的資料頁在記憶體中,則修改後返回對應的結果(當然還有後續操作)。如果不在的話,則會從磁碟裡將該行所對應的資料頁讀到記憶體中再進行修改。

好了,現在讓我們回到開始時候的問題。為什麼操作磁碟慢,但是 SQL 執行卻不慢呢。到這裡相信你也差不多知道了吧。

緩衝池的存在,很大程度減少了磁碟 I/O 帶來的開銷。要操作的資料行所在的資料頁如果存在於快取中的話,就不需要從磁碟中進行讀取。這樣在執行後就可以很快拿到結果。

緩衝池的預讀機制

我們可以看出來,只要不存在或減少磁碟 I/O,執行速度自然就會變快。那麼對於載入資料頁這種無法避免的磁碟 I/O 來說是否有更好的方式呢?既然避免不了,那減少磁碟 I/O 的次數總可以吧?

這就是我們要講的 Mysql 中「預讀」的新特性,它是 Innodb 通過在緩衝池中提前讀取多個資料頁來優化 I/O 的一種方式。因為磁碟讀寫的時候,是按照頁的方式來讀取的(你可以理解為固定大小的資料,例如一頁資料為 16K),每次至少讀入一頁的資料,如果下次讀取的資料就在頁中,就不用再去磁碟上讀取了,從而減少了磁碟 I/O。

可以在命令列通過如下命令檢視對應的頁大小:

緩衝池的空間管理

你可能會有疑問,緩衝池這麼洋氣的東西,為什麼不把所有的資料都放到緩衝池裡呢?這樣速度豈不是美滋滋,放到磁碟裡慢的跟老牛拉車一樣。 

哎,哥,醒醒,拋開記憶體的易失性不談,緩衝池也是有大小限制的。那你可能又有疑惑了,既然緩衝池有大小限制,那我每次都讀入的資料頁怎麼來管理呢。別的資料頁都佔了地兒了,哪有我的位置?

這裡我們來聊聊緩衝池的空間管理,其實對緩衝池進行管理的關鍵部分是如何安排進池的資料並且按照一定的策略淘汰池中的資料,保證池中的資料不“溢位”,同時還能保證常用資料留在池子中。

 

傳統 LRU 淘汰法 

緩衝池是基於傳統的 LRU 方法來進行快取頁管理的,我們先來看下如果使用 LRU 是如何管理的。

LRU,全稱是 Least Recently Used,中文名字叫作「最近最少使用」。從名字上就很容易理解了。

這裡分兩種情況:

(1)快取頁已在緩衝池中

這種情況下會將對應的快取頁放到 LRU 連結串列的頭部,無需從磁碟再進行讀取,也無需淘汰其它快取頁。

如下圖所示,如果要訪問的資料在 6 號頁中,則將 6 號頁放到連結串列頭部即可,這種情況下沒有快取頁被淘汰。

 (2)快取頁不在緩衝池中

快取頁不在緩衝中,這時候就需要從磁碟中讀入對應的資料頁,將其放置在連結串列頭部,同時淘汰掉末尾的快取頁 

如下圖所示,如果要訪問的資料在 60 號頁中,60 號頁不在緩衝池中,此時載入進來放到連結串列的頭部,同時淘汰掉末尾的 17 號快取頁。

 

是不是看上去很簡單,同時也能滿足緩衝池淘汰快取頁的方法?但是我們來思考幾個問題:

  • 預讀失效

上面我們提到了緩衝池的預讀機制可能會預先載入相鄰的資料頁。假如載入了 20、21 相鄰的兩個資料頁,如果只有頁號為 20 的快取頁被訪問了,而另一個快取頁卻沒有被訪問。此時兩個快取頁都在連結串列的頭部,但是為了載入這兩個快取頁卻淘汰了末尾的快取頁,而被淘汰的快取頁卻是經常被訪問的。這種情況就是預讀失效,被預先載入進緩衝池的頁,並沒有被訪問到,這種情況是不是很不合理。

  • 緩衝池汙染 

還有一種情況是當執行一條 SQL 語句時,如果掃描了大量資料或是進行了全表掃描,此時緩衝池中就會載入大量的資料頁,從而將緩衝池中已存在的所有頁替換出去,這種情況同樣是不合理的。這就是緩衝池汙染,並且還會導致 MySQL 效能急劇下降。

冷熱資料分離

這樣看來,傳統的 LRU 方法並不能滿足緩衝池的空間管理。因此,Msyql 基於 LRU 設計了冷熱資料分離的處理方案。

也就是將 LRU 連結串列分為兩部分,一部分為熱資料區域,一部分為冷資料區域。

 

當資料頁第一次被載入到緩衝池中的時候,先將其放到冷資料區域的連結串列頭部,1s(由 innodb_old_blocks_time 引數控制) 後該快取頁被訪問了再將其移至熱資料區域的連結串列頭部。

可能你會有疑惑了,為什麼要等 1s 後才將其移至熱資料區域呢?你想想,如果資料頁剛被載入到冷資料區就被訪問了,之後再也不訪問它了呢?這不就造成熱資料區的浪費了嗎?要是 1s 後不訪問了,說明之後可能也不會去頻繁訪問它,也就沒有移至熱緩衝區的必要了。當快取頁不夠的時候,從冷資料區淘汰它們就行了。 

另一種情況,當我的資料頁已經在熱緩衝區了,是不是快取頁只要被訪問了就將其插到連結串列頭部呢?不用我說你肯定也覺得不合理。熱資料區域裡的快取頁是會被經常訪問的,如果每訪問一個快取頁就插入一次連結串列頭,那整個熱緩衝區裡就異常騷動了,你想想那個畫面。

那咋整呢?Mysql 中優化為熱資料區的後 3/4 部分被訪問後才將其移動到連結串列頭部去,對於前 1/4 部分的快取頁被訪問了不會進行移動。

好了,到這裡關於 buffer pool  的知識就講完了。這期裡我們講了 buffer pool 能使 SQL 執行變快的原因,同時還講了有關 buffer pool 空間的管理方式。歡迎在留言區裡進行討論。

總結 

緩衝池的應用

  • 緩衝池很大程度減少了磁碟 I/O 帶來的開銷,通過將操作的資料行所在的資料頁載入到緩衝池可以提高 SQL 的執行速度。

緩衝池的預讀機制

  • 為了減少磁碟 I/O,Innodb 通過在緩衝池中提前讀取多個資料頁來進行優化,這種方式叫作預讀。

緩衝池的空間管理

  • 傳統的LRU方法對於緩衝池來說,會導致預讀失效和緩衝池汙染兩種情況,因此這種傳統的方式並不適用緩衝池的空間管理。

  • 基於對 LRU 方法的優化,Msyql 設計了冷熱資料分離的處理方案,將LRU連結串列分為熱資料區和冷資料區兩部分,這樣就可以解決預讀失效和緩衝池汙染的情況。 

關於作者

作者:大家好,我是萊烏,BAT搬磚工一枚。從小公司進入大廠,一路走來收穫良多,想將這些經驗分享給有需要的人,因此建立了公眾號IT界農民工】。定時更新,希望能幫助到你。同時,我給大家肝了一份Redis面經手冊,在我的公眾號內回覆【pdf】即可獲取,希望對你有所幫助。

 

相關文章