【Mysql】InnoDB 引擎中的資料頁結構

把蘋果v咬哭發表於2021-07-23

InnoDB 是 mysql 的預設引擎,也是我們最常用的,所以基於 InnoDB,學習頁結構。而學習頁結構,是為了更好的學習索引

一、頁的簡介

頁是 InnoDB 管理儲存空間的基本單位,一個頁的大小一般是 16kb。

為了達成不同的目的,作者設計了多種型別的頁,比如:

  • 存放表空間頭部資訊的頁
  • 存放 change buffer 資訊的頁
  • 存放 inode 資訊的頁
  • 存放 undo 日誌資訊的頁
  • ... ...

然而我們最關心的,還是那些存放進表中那些資料記錄是在哪種頁上,官方稱這種存放記錄的頁為索引(INDEX)頁,但是為了便於理解,本篇暫把它稱為資料頁

二、資料頁的結構

這資料頁也有 16kb 的儲存空間,可以大致劃分為 7 個部分。

從結構圖中可以看到,有些部分的佔用位元組數是確定的,有的是不確定的。我們最關心的使用者儲存的記錄,在 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 記錄。

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

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




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

相關文章