首頁 > 軟體

MySQL的InnoDB儲存引擎的資料頁結構詳解

2022-03-02 19:02:47

1 InnoDB頁的概念

InnoDB是一個將表中的資料儲存在磁碟上的儲存引擎,即使我們關閉並重啟伺服器,資料還是存在。而真正處理資料的過程發生在記憶體中,所以需要把磁碟中的資料載入到記憶體中,所以需要把磁碟中的資料載入到記憶體中。如果處理寫入和修改請求,還需要將記憶體中的內容重新整理到磁碟上。而我們知道讀寫磁碟的速度非常慢,與讀寫記憶體差了幾個數量級。當我們想從表中獲取某些記錄時,InnoDB儲存引擎需要一條一條的把記錄從磁碟上讀出來麼?不,那樣會慢死,InnoDB採取的方式是,將資料劃分為若干個頁,以頁作為磁碟和記憶體之間互動的基本單位。InnoDB中頁的大小一般為16KB。也就是在一般情況下,一次最少從磁碟中讀取16KB的內容到記憶體中,一次最少把記憶體中的16KB內容重新整理到磁碟中。

2 資料頁的結構

存放表中記錄的叫索引頁也叫資料頁,資料頁代表的這塊16KB大小的儲存空間可以劃分為多個部分,不同部分有不同功能。

我們自己儲存的記錄會按照指定的行格式儲存到User Records部分,但是一開始生成頁的時候,其實並沒有User Records部分,每當插入一條記錄時,都會從Free Space部分申請一個記錄大小的空間,並將這個空間劃分到User Records部分。當Free Records部分的空間全部被User Records部分替代掉之後,也就意味著這個頁被用完了,此時如果還有新的記錄插入,就需要去申請頁了。

3 記錄在頁中的儲存

假如向page_demo表中插入4條記錄,那麼這4條記錄的儲存方式為:

insert into page_demo values(1,100,'aaaa'),(2,200,'bbbb'),(3,300,'cccc'),(4,400,'dddd');

無論向頁中插入了多少條記錄,InnoDB規定,任何使用者記錄都比infimum記錄大,任何使用者記錄都不supermum小。

通過記錄的儲存方式可以看到,記錄按照主鍵從小到大的順序形成了一個單向連結串列,通過一條記錄可以找到它的下一條記錄,下一條記錄指的並不是插入順序中的下一條記錄,而是按照主鍵值由小到大的順序排列的下一條記錄,而且規定infimum記錄的下一條記錄就是本頁中主鍵值最小的使用者記錄,本頁中主鍵值最大的使用者記錄的下一條記錄就是supermum記錄,supermum記錄是單向連結串列中的最後一個節點。

無論怎麼對頁中的記錄進行增刪改查操作,InnoDB始終會維護記錄的一個單向連結串列,連結串列中的各個節點是按照主鍵值由小到大的順序連結起來的。

4 Page Directory頁目錄

我們知道記錄頁是按照主鍵值由小到大的順序串聯成了單向連結串列,如果想根據主鍵值查詢頁中的某條記錄,該咋辦呢?比如下面的查詢語句:

select * from page_demo where c1=3;

最笨的方法就是從Infimum記錄開始,沿著單向連結串列一直往後找,而且在找的時候可以投機取巧,因為連結串列中各個記錄的值是按照從小到大的順序排列的,所以當連結串列中的某個節點記錄的主鍵值大於想要查詢的主鍵值時,就可以停止查詢了。

當頁中儲存的記錄數量比較少時,這種方法用起來沒有啥問題,但是,如果一個頁中儲存了非常多的記錄,遍歷操作對效能來說還是有損耗的,所以遍歷查詢是一個笨方法,為此InnoDB設計了Page Directory頁目錄。

(1) 將所有正常的記錄(包括infinmumsupermum記錄)劃分為幾個組。InnoDB對每個分組的條數是有規定的,infimum記錄所在的分組只能有一條記錄,supermum記錄所在的分組擁有的記錄數只能在18條之間,剩下的分組中記錄的條數範圍只能在48之間。

(2) 將每個組中最後一條記錄在頁面中的地址偏移量單獨提取出來按順序儲存到靠近頁尾部的地方,這個地方就是Page Directory

比如,現在page_demo有6條記錄,InnoDB會把他們分成2個組,第一組只有一個infimum記錄,第二組是剩餘的5條記錄,2個組就對應著兩個槽,每個槽存放著每個組中最大的那條記錄在頁面中的地址偏移量。

由於現在page_demo表中的記錄太少,無法掩飾在新增頁目錄之後是如何加快查詢速度的,所以再往page_demo表中新增一些記錄。

insert into page_demo 
values(1,100,'aaaa'),(2,200,'bbbb'),(3,300,'cccc'),(4,400,'dddd'),(5,500,'eeee'),(6,600,'ffff'),(7,700,'gggg'),(8,800,'hhhh'),(9,900,'iiii'),(10,1000,'jjjj'),(11,1100,'kkkk'),(12,1200,'llll'),(13,1300,'mmmm'),(14,1400,'nnnn'),(15,1500,'oooo'),
(16,1600,'pppp')

現在頁中就一共有18條記錄了(包括infimum記錄和supermum記錄),這些記錄被分成了5個組,因為各個槽之間是挨著的,而且他們代表的記錄的主鍵值都是從小到大排序的,所以可以使用二分法來快速查詢。5個槽的編號跟別為0,1,2,3,4,所以初始情況下最低的槽就是low=0,最高的槽就是high=4,假如我們想要尋找主鍵值為6的記錄,過程就是這樣的:

(1) 計算中間槽的位置:(0+4)/2=2,檢視槽2對應記錄的主鍵值8;又因為8>6,所以設定high=2,low保持不變。

(2) 重新計算中間槽的位置:(0+2)/2=1,檢視槽1對應的記錄的主鍵值為4,又因為4<6,所以設定low=1,high保持不變。

(3) 因為high-low=1,所以主鍵值為6的記錄在槽2對應的組中,此時需要找到槽2所在分組中主鍵值最小的那條記錄,然後沿著單向連結串列遍歷槽2中的記錄。

綜上所述,在一個資料頁中查詢指定主鍵值的記錄時,過程分為2步:

(1) 通過二分法確定該記錄所在分組對應的槽,然後找到該槽所在分組中主鍵值最小的那條記錄。

(2) 通過記錄的next_record屬性遍歷該槽所在分組中的各個記錄。

5 File Header檔案頭部

InnoDB是以頁為單位存放資料的,有時在存放某種型別的資料時,佔用的空間非常大。InnoDB可能無法一次性為這麼多資料分配一個非常大的儲存空間,而如果分散到多個不連續的頁中進行儲存,則需要把這些也關聯起來,FIL_PAGE_PREVFIL_PAGE_NEXT就分別代表本資料頁的上一個頁和下一個頁的頁號。這樣通過建立一個雙向連結串列就把許許多多的頁串聯起來了,而無須這些也在物理上真正連著。所以儲存記錄的資料頁其實可以組成一個雙向連結串列。

6 InnoDB頁和記錄的關係

各個資料頁可以組成一個雙向連結串列,而每個資料頁中的記錄會按照主鍵值從小到大的順序組成一個單向連結串列,每個資料頁都會為儲存在它裡面的記錄生成一個頁目錄,在通過主鍵查詢某條記錄的時候可以在頁目錄中使用二分法快速定位到對應的槽,然後遍歷該槽對應分組中的記錄即可快速找到指定的記錄。

7 沒有索引時查詢記錄

1、在一個頁中查詢:

假設現在表中的記錄較少,所有的記錄都可以存放在一個頁中,在查詢記錄時,可以根據搜尋條件的不同可以分為兩種情況:

(1) 以主鍵為搜尋條件:可以在頁目錄中使用二分法快速定位到指定的槽,然後遍歷該槽對應分組中的記錄,即可快速定位到指定的記錄。

(2) 以其他列作為搜尋條件:對於非主鍵列的查詢就沒有那麼幸運了,因為在資料頁中並沒有為非主鍵列建立所謂的頁目錄,所以無法通過二分法快速定位相應的槽,在這種情況下只能從Infimum記錄開始依次遍歷單向連結串列中的每條記錄,然後對比每條記錄是否符合搜尋條件,這種查詢的效率非常低。

2、在很多頁中查詢:

在很多時候,表中存放的記錄都是非常多的,需要用到好多的資料頁來儲存這些記錄。在很多頁中查詢記錄可以分為兩個步驟:

(1) 定位到記錄所在的頁;

(2) 從所在的頁內查詢相應的記錄;

在沒有索引的情況下,無論是根據主鍵列還是其他的列進行查詢,由於我們不能快速的定位到記錄所在的頁,所以只能從第一頁沿著雙向連結串列一直往下找。在每一頁中我們根據上面說的查詢方式去查詢指定的記錄。因為要遍歷所有的資料頁,所以這種方式顯然是超級耗時的。如果一個表有一億條記錄,使用這種方式去查詢記錄,估計要到猴年馬月才能查到結果,所以就需要一種能高效完成搜尋的方法,即索引。

總結

本篇文章就到這裡了,希望能夠給你帶來幫助,也希望您能夠多多關注it145.com的更多內容!  


IT145.com E-mail:sddin#qq.com