首頁 > 軟體

Mysql InnoDB引擎中頁目錄和槽的查詢過程

2022-05-31 14:03:08

Mysql InnoDB引擎頁目錄

一、頁目錄和槽

從結構圖中可以看到,有些部分的佔用位元組數是確定的,有的是不確定的。我們最關心的使用者儲存的記錄,在 User Records部分。

不過,在一開始生成頁的時候,並沒有 User Records 部分。當有新的記錄插入時,就會從 Free Space部分申請一個記錄大小的空間,然後劃分到 User Records 部分,直到 Free Space 全部被 User Records 替代,表示這個頁已經用完。如果還有新的記錄插入,需要申請新的頁。

我覺得這裡可以把這個資料頁當作是書本的頁,書頁上的內容通常是一行行的呈現,當整個頁都用完了,就得翻到下一頁(新頁)去繼續寫了。

三、記錄在頁中的儲存結構

那麼,User Records 部分裡的這些記錄,是如何管理的呢?

先來建一張表:

CREATE TABLE pingguo_demo( 	c1 INT, 	c2 INT, 	c3 VARCHAR(10000), 	PRIMARY KEY (c1) 	) CHARSET = ASCII ROW_FORMAT = COMPACT;

這裡的指定使用行格式為 COMPACT(引擎中還存在其他的行格式),暫且知道 COMPACT 即可。

當我們在資料庫的插入了一條記錄後,其實背後的行格式是這樣的:

注意這裡橙色標識的記錄頭資訊,它又包含了很多重要資訊:

  • 預留位1:佔用 1 位元,沒有使用。
  • 預留位2:佔用 1 位元,沒有使用。
  • deleted_flag:佔用 1 位元,標記該記錄是否被刪除。
  • min_rec_flag:佔用 1 位元,在 B+ 樹(後面索引會講到)中每層非 葉子節點中的最小的目錄項,都會新增此標記。
  • n_owned:一個頁面中的記錄被分為若干個組,每個組裡有一個記錄是“大哥”,其他記錄都是“小弟”。而這位“大哥”記錄的 n_owned 就是所在組的所有記錄條數,而小弟們的 n_owned 都是 0
  • heap_no:佔用 13 位元,表示當前記錄在頁面堆中的相對位置。
  • record_type:佔用 3 位元,表示當前記錄的類型,0是普通記錄,1是 B+樹非葉節點的目錄項記錄,2是 Infimum 記錄,3是 Suprememum 記錄。
  • next_record:佔用 16 位元,表示下一條記錄的相對位置。

四、記錄頭資訊

現在,向上面新建的表中插入 4 條記錄:

INSERT INTO pingguo_demo VALUES 	(1, 100, 'aaaa'), 	(2, 200, 'bbbb'), 	(3, 300, 'cccc'), 	(4, 400, 'dddd');

那麼,對應這4條記錄的行格式應該為:

注意,這裡為了便於記憶,作了簡化。另外,記錄中的資訊實際是二進位制位資料,這裡為了理解寫的是十進位制。而且,各條記錄在 User Records 中儲存是沒有空隙的,這裡抽象表示。

1. deleted_flag

這個屬性用來標記當前記錄是否被刪除,1 表示被刪除,0 表示沒有被刪除。

嗯?我表裡刪除了資料居然還在頁裡。

是的,你以為被刪除了,其實還在磁碟上。為什麼呢?

因為如果在磁碟上移除這些記錄,還要再重新排列其他記錄,會帶來效能消耗,所以只打了一個刪除的標記。

然後,所有的刪除的記錄會組成一個垃圾連結串列。而記錄在這個連結串列中所佔用的空間稱為可重用空間,當後面有新記錄插入到表中,它們就可能覆蓋掉這些空間。

2. min_rec_flag

在 B+ 樹中每層非葉子節點中的最小的目錄項,都會新增此標記。這裡說的目錄項,要後續講解。

這裡4條記錄的 min_rec_flag 都是 0,表示都不是 B+ 樹非葉子節點中的最小的目錄項記錄。

3. n_owned

要下一章講解。

4. heap_no

表示當前記錄在頁面堆中的相對位置。

上面的4條記錄是抽象的描述,實際上這些記錄都是一條一條緊密無縫排列在一起的,這就是堆(heap)。

為了方便管理,把一條記錄在堆中的相對位置稱為 heap_no。

  • 在頁面前面的記錄 heap_no 相對較小
  • 在頁面後面的記錄 heap_no 相對較大
  • 每申請一條記錄的儲存空間時,該記錄比物理位置在它之前的那條記錄的 heap_no 值大 1

上述 4 條記錄的 heap_no 分別為 2、3、4、5,嗯?怎麼沒有 0 和 1?

虛擬記錄-Infimum 和 Supremum

這個在本文第二部分有提到過。其實這2條記錄是頁裡自動新增的:

Infimum:代表頁面中的最小記錄

Supremum:代表頁面中的最大記錄

作者規定,無論向頁中插入了多少條記錄,任何使用者記錄都比 Infimum 記錄大,都比 Supremum 記錄小。

這 2 條虛擬記錄的結構也很簡單。

所以,對於上面插入的 4 條使用者記錄,還應該加上這2個預設記錄,而且位置最靠前。

另外,還需要注意,當堆中記錄的 heap_no 值分配後,就不會發生改動。即使刪除了堆中的某條記錄,這條被刪記錄的 heap_no 值也仍然不變。

5. record_type

這個屬性表示當前記錄的類型,共 4 種:

0:表示普通記錄1:表示 B+ 樹非葉節點的目錄項記錄2:表示 Infimum 記錄3:表示 Supremum 記錄

6. next_record

這個屬性很重要,表示從當前記錄的真實資料到下一條記錄的真實資料之間的距離。

  • 屬性值為正數:說明當前記錄的下一條記錄在當前記錄的後面。
  • 屬性值為負數:說明當前記錄的下一條記錄在當前記錄的前面。

比如,第 1 條記錄的 next_record 值為 32,那麼從此記錄的真實資料地址向後找 32 位元組就是下一條記錄的真實資料。再比如,當值為 -111,那麼就代表從此記錄向前找 111 位元組。

很熟悉?沒錯,就是連結串列。

  • 下一條記錄,是指按照主鍵從小到大排列的下一條。
  • Infrimum 記錄的下一條記錄,就是本頁中主鍵值最小的使用者記錄。
  • 本頁主鍵值最大的使用者記錄的下一條記錄,就是 Supremum 記錄。

所以,現在再來重新看下記錄之間的示意圖,可以用單向連結串列來描述了:

如果這時候,刪掉其中的某條記錄,改變的是指針。

本文參考書籍:《mysql是怎樣運行的》

以上就是Mysql InnoDB引擎中的資料頁結構詳解的詳細內容,更多關於Mysql InnoDB引擎資料頁結構的資料請關注it145.com其它相關文章!

" target="_blank">接上一篇
,現在知道記錄在頁中按照主鍵大小順序串成了單連結串列。

那麼我使用主鍵查詢的時候,最順其自然的辦法肯定是從第一條記錄,也就是 Infrimum 記錄開始,一直向後找,只要存在總會找到。這種在資料量少的時候還好說,一旦資料多了,遍歷耗時一定非常長。

於是,作者又想到了一個好辦法,靈感來自於書本中的目錄。我們翻書的時候想查詢一些內容,就會去檢視目錄,然後直接確定好內容所在的頁碼。

那麼對於 InnoDB 來說,過程如下:

  • 將所有正常的記錄劃分為幾個組,這裡包括那 2 條虛擬記錄,但是不包含已經被移除到垃圾連結串列的記錄。
  • 每個組內最後一條記錄(也就是最大的那條)就是“大哥”,其他記錄都是“小弟”,而“大哥”記錄的頭資訊中的 n_owned 屬性表示該組內共有幾條記錄。
  • 將每個組中最後一條記錄在頁面中的地址偏移量單獨提取出來,按順序儲存到靠近頁尾部的地方。

這個地方就是頁目錄 Page Directory。而上述的地址偏移量就是該記錄的真實資料與頁面中第 0 個位元組之間的距離,這些地址偏移量被稱為槽。

每個槽佔用 2 位元組,頁目錄就是由多個槽組成。

二、頁目錄的規定

在上一篇中,建立的表裡存在 4 條資料,那麼在頁中還要算上 Infimum 和 Supremum,共 6 條記錄。

這時候 InnoDB 會把它們分出 2 個組:

  • 第一組:只有一個 Infimum 記錄
  • 第二組:剩下的 5 條記錄

每個槽中,存放著每個組裡最大的那條記錄所在頁面中的地址偏移量。

從圖中,需要關注頁目錄的一些點:

  • 頁目錄有 2 個槽,說明記錄被分為 2 個組。
  • Infimum 記錄的 n_owned 屬性值為 1,而 Supremum 的為 5。

為什麼這 6 條記錄要這樣分?因為作者對於每組中的記錄數量有規定:

  • 對於 Infimum 所在的分組只能有 1 條記錄。
  • Supremum 所在的分組只能在 1~8 條之間。
  • 剩下的分組,記錄條數範圍只能是 4~8 之間。

三、頁目錄查詢記錄的過程

現在繼續向測試表裡插入 12 條資料,也就是說在頁中共有 18 條記錄。

然後這些記錄就被分成了 5 個組,這裡參考書籍上的示意圖(只保留一些關鍵屬性):

現在,要查詢主鍵是 6 的記錄,要如何進行?

因為 5 個槽的編號分別為 0、1、2、3、4 挨著的,並且裡面的主鍵值也都是從小到大進行排序的,可以使用二分法(不清楚的可以百度),那麼初始情況下 low=0,high=4:

  • 計算中間槽的位置,(0+4)/ 2=2,於是檢視槽 2 對應記錄的主鍵值為 8,因為 8 > 6,所以 high = 2,low 不變。
  • 重新計算中間槽位置,(0+2)/ 2=1,於是檢視槽 1 對應記錄的主鍵為4,因為 4 < 6,所以 high 不變,low = 1。
  • 因為 high - low = 1,所以確定主鍵值為6 的記錄就在槽 2 對應的組中。接著找到該組中主鍵最小的記錄,沿著單連結串列向後遍歷,最終找到主鍵 6 的記錄。

這裡有個問題,槽對應的值都是這個組的主鍵最大的記錄,如何找到組裡最小的記錄?比如槽 2 對應最大主鍵是 8 的記錄,那如何找到最小記錄。

解決辦法是:

  • 通過槽 2 找到 槽 1 對應的記錄,也就是主鍵為 4 的記錄。
  • 主鍵為 4 的記錄的下一條記錄就是槽 2 當中主鍵最小的記錄,可以找到主鍵 5。

總結

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

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

通過記錄的 next_record 屬性比那裡該槽所在組的各個記錄,最終找到目標記錄。

本文參考書籍: 《mysql是怎樣執行的》

以上就是Mysql InnoDB引擎中頁目錄和槽的查詢過程的詳細內容,更多關於Mysql InnoDB引擎頁目錄的資料請關注it145.com其它相關文章!


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