13 從磁盤讀取數(shù)據(jù)頁到Buffer Pool的時候,free鏈表有什么用?

從磁盤讀取數(shù)據(jù)頁到Buffer Pool的時候,free鏈表有什么用?
1、數(shù)據(jù)庫啟動的時候,是如何初始化Buffer Pool的?
現(xiàn)在我們已經(jīng)搞明白一件事兒了,那就是數(shù)據(jù)庫的Buffer Pool到底長成個什么樣,大家想必都是理解了
其實(shí)說白了,里面就是會包含很多個緩存頁,同時每個緩存頁還有一個描述數(shù)據(jù),也可以叫做是控制數(shù)據(jù),但是我個人是比較傾向于叫做描述數(shù)據(jù),或者緩存頁的元數(shù)據(jù),都是可以的。
那么在數(shù)據(jù)庫啟動的時候,他是如何初始化Buffer Pool的呢?
其實(shí)這個也很簡單,數(shù)據(jù)庫只要一啟動,就會按照你設(shè)置的Buffer Pool大小,稍微再加大一點(diǎn),去找操作系統(tǒng)申請一塊內(nèi)存區(qū)域,作為Buffer Pool的內(nèi)存區(qū)域。
然后當(dāng)內(nèi)存區(qū)域申請完畢之后,數(shù)據(jù)庫就會按照默認(rèn)的緩存頁的16KB的大小以及對應(yīng)的800個字節(jié)左右的描述數(shù)據(jù)的大小,在Buffer Pool中劃分出來一個一個的緩存頁和一個一個的他們對應(yīng)的描述數(shù)據(jù)。
然后當(dāng)數(shù)據(jù)庫把Buffer Pool劃分完畢之后,看起來就是之前我們看到的那張圖了,如下圖所示。
? ? ? ? ? ?

? ? ? ? ? ? ?
只不過這個時候,Buffer Pool中的一個一個的緩存頁都是空的,里面什么都沒有,要等數(shù)據(jù)庫運(yùn)行起來之后,當(dāng)我們要對數(shù)據(jù)執(zhí)行增刪改查的操作的時候,才會把數(shù)據(jù)對應(yīng)的頁從磁盤文件里讀取出來,放入Buffer Pool中的緩存頁中。
2、我們怎么知道哪些緩存頁是空閑的呢?
接著我們來看下一個問題,當(dāng)你的數(shù)據(jù)庫運(yùn)行起來之后,你肯定會不停的執(zhí)行增刪改查的操作,此時就需要不停的從磁盤上讀取一個一個的數(shù)據(jù)頁放入Buffer Pool中的對應(yīng)的緩存頁里去,把數(shù)據(jù)緩存起來,那么以后就可以對這個數(shù)據(jù)在內(nèi)存里執(zhí)行增刪改查了。
但是此時在從磁盤上讀取數(shù)據(jù)頁放入Buffer Pool中的緩存頁的時候,必然涉及到一個問題,那就是哪些緩存頁是空閑的?
因?yàn)槟J(rèn)情況下磁盤上的數(shù)據(jù)頁和緩存頁是一 一對應(yīng)起來的,都是16KB,一個數(shù)據(jù)頁對應(yīng)一個緩存頁。
所以我們必須要知道Buffer Pool中哪些緩存頁是空閑的狀態(tài)。
所以數(shù)據(jù)庫會為Buffer Pool設(shè)計(jì)一個free鏈表,他是一個雙向鏈表數(shù)據(jù)結(jié)構(gòu),這個free鏈表里,每個節(jié)點(diǎn)就是一個空閑的緩存頁的描述數(shù)據(jù)塊的地址,也就是說,只要你一個緩存頁是空閑的,那么他的描述數(shù)據(jù)塊就會被放入這個free鏈表中。
剛開始數(shù)據(jù)庫啟動的時候,可能所有的緩存頁都是空閑的,因?yàn)榇藭r可能是一個空的數(shù)據(jù)庫,一條數(shù)據(jù)都沒有,所以此時所有緩存頁的描述數(shù)據(jù)塊,都會被放入這個free鏈表中
我們看下圖所示
? ? ? ? ? ?

? ? ? ? ? ? ?
大家可以看到上面出現(xiàn)了一個free鏈表,這個free鏈表里面就是各個緩存頁的描述數(shù)據(jù)塊,只要緩存頁是空閑的,那么他們對應(yīng)的描述數(shù)據(jù)塊就會加入到這個free鏈表中,每個節(jié)點(diǎn)都會雙向鏈接自己的前后節(jié)點(diǎn),組成一個雙向鏈表。
除此之外,這個free鏈表有一個基礎(chǔ)節(jié)點(diǎn),他會引用鏈表的頭節(jié)點(diǎn)和尾節(jié)點(diǎn),里面還存儲了鏈表中有多少個描述數(shù)據(jù)塊的節(jié)點(diǎn),也就是有多少個空閑的緩存頁。
3、free鏈表占用多少內(nèi)存空間?
可能有的人會以為這個描述數(shù)據(jù)塊,在Buffer Pool里有一份,在free鏈表里也有一份,好像在內(nèi)存里有兩個一模一樣的描述數(shù)據(jù)塊,是么?
其實(shí)這么想就大錯特錯了。
這里要給大家講明白一點(diǎn),這個free鏈表,他本身其實(shí)就是由Buffer Pool里的描述數(shù)據(jù)塊組成的,你可以認(rèn)為是每個描述數(shù)據(jù)塊里都有兩個指針,一個是free_pre,一個是free_next,分別指向自己的上一個free鏈表的節(jié)點(diǎn),以及下一個free鏈表的節(jié)點(diǎn)。
通過Buffer Pool中的描述數(shù)據(jù)塊的free_pre和free_next兩個指針,就可以把所有的描述數(shù)據(jù)塊串成一個free鏈表,大家可以自己去思考一下這個問題。上面為了畫圖需要,所以把描述數(shù)據(jù)塊單獨(dú)畫了一份出來,表示他們之間的指針引用關(guān)系。
對于free鏈表而言,只有一個基礎(chǔ)節(jié)點(diǎn)是不屬于Buffer Pool的,他是40字節(jié)大小的一個節(jié)點(diǎn),里面就存放了free鏈表的頭節(jié)點(diǎn)的地址,尾節(jié)點(diǎn)的地址,還有free鏈表里當(dāng)前有多少個節(jié)點(diǎn)。
4、如何將磁盤上的頁讀取到Buffer Pool的緩存頁中去?
好了,現(xiàn)在我們可以來解答這一篇文章的最后一個問題了,當(dāng)你需要把磁盤上的數(shù)據(jù)頁讀取到Buffer Pool中的緩存頁里去的時候,是怎么做到的?
其實(shí)有了free鏈表之后,這個問題就很簡單了。
首先,我們需要從free鏈表里獲取一個描述數(shù)據(jù)塊,然后就可以對應(yīng)的獲取到這個描述數(shù)據(jù)塊對應(yīng)的空閑緩存頁,我們看下圖所示。
? ? ? ? ? ?

? ? ? ? ? ? ?
接著我們就可以把磁盤上的數(shù)據(jù)頁讀取到對應(yīng)的緩存頁里去,同時把相關(guān)的一些描述數(shù)據(jù)寫入緩存頁的描述數(shù)據(jù)塊里去,比如這個數(shù)據(jù)頁所屬的表空間之類的信息,最后把那個描述數(shù)據(jù)塊從free鏈表里去除就可以了,如下圖所示。
? ? ? ? ? ?

? ? ? ? ? ? ?
可能有朋友還是疑惑,這個描述數(shù)據(jù)塊是怎么從free鏈表里移除的呢?
簡單,我給你一段偽代碼演示一下。
假設(shè)有一個描述數(shù)據(jù)塊02,他的上一個節(jié)點(diǎn)是描述數(shù)據(jù)塊01,下一個節(jié)點(diǎn)是描述數(shù)據(jù)塊03,那么他在內(nèi)存中的數(shù)據(jù)結(jié)構(gòu)如下。

現(xiàn)在假設(shè)block03被使用了,要從free鏈表中移除,那么此時直接就可以把block02節(jié)點(diǎn)的free_next設(shè)置為null就可以了,block03就從free鏈表里失去引用關(guān)系了,如下所示。

想必看到這里,大家就完全明白,磁盤中的數(shù)據(jù)頁是如何讀取到Buffer Pool中的緩存頁里去的了,而且這個過程中free鏈表是用來干什么的。
5、你怎么知道數(shù)據(jù)頁有沒有被緩存?
接著我們來看下一個問題:你怎么知道一個數(shù)據(jù)頁有沒有被緩存呢?
我們在執(zhí)行增刪改查的時候,肯定是先看看這個數(shù)據(jù)頁有沒有被緩存,如果沒被緩存就走上面的邏輯,從free鏈表中找到一個空閑的緩存頁,從磁盤上讀取數(shù)據(jù)頁寫入緩存頁,寫入描述數(shù)據(jù),從free鏈表中移除這個描述數(shù)據(jù)塊。
但是如果數(shù)據(jù)頁已經(jīng)被緩存了,那么就會直接使用了。
所以其實(shí)數(shù)據(jù)庫還會有一個哈希表數(shù)據(jù)結(jié)構(gòu),他會用表空間號+數(shù)據(jù)頁號,作為一個key,然后緩存頁的地址作為value。
當(dāng)你要使用一個數(shù)據(jù)頁的時候,通過“表空間號+數(shù)據(jù)頁號”作為key去這個哈希表里查一下,如果沒有就讀取數(shù)據(jù)頁,如果已經(jīng)有了,就說明數(shù)據(jù)頁已經(jīng)被緩存了。
我們看下圖,又引入了一個數(shù)據(jù)頁緩存哈希表的結(jié)構(gòu)。
也就是說,每次你讀取一個數(shù)據(jù)頁到緩存之后,都會在這個哈希表中寫入一個key-value對,key就是表空間號+數(shù)據(jù)頁號,value就是緩存頁的地址,那么下次如果你再使用這個數(shù)據(jù)頁,就可以從哈希表里直接讀取出來他已經(jīng)被放入一個緩存頁了。
? ? ? ? ? ?

? ? ? ? ? ? ?
6、今日思考題
今天我們給大家留一個思考題,大家去想一個問題,我們要取一個數(shù)據(jù)的時候,必然會取他所屬的一個數(shù)據(jù)頁,而且這個數(shù)據(jù)必然是屬于一個表的,所以我們在上面初步引入了一個表空間的概念
也就是說我們寫SQL的時候,只知道表+行的概念,但是在MySQL內(nèi)部操作的時候,是表空間+數(shù)據(jù)頁的概念。
那么大家覺得這兩者之間的區(qū)別是什么?他們之間的聯(lián)系是什么?
請大家積極在評論區(qū)寫下你的思考,多跟其他同學(xué)在評論區(qū)中交流。
End
專欄版權(quán)歸公眾號儒猿技術(shù)窩所有
未經(jīng)許可不得傳播,如有侵權(quán)將追究法律責(zé)任