MySQL系列(4)— InnoDB數據頁結構

系列文章:MySQL系列專欄web

InnoDB 數據頁

咱們前面簡單提到了的概念, 是InnoDB存儲引擎管理數據庫的最小磁盤單位,一個頁的大小通常是16KB。一次至少讀取一頁的數據到內存,或者刷新一頁的數據到磁盤。數據庫

咱們這節主要來看存放數據記錄的頁,也就是 INDEX 類型的數據頁。markdown

數據頁結構

數據頁由 7 個部分組成,大體以下圖所示:性能

image.png

其中 File Header、Page Header、File Trailer 的大小是固定的,分別爲 3八、5六、8字節User Records、Free Space、Page Directory 這些部分爲實際的行記錄存儲空間,所以大小是動態的。url

記錄頭信息

前面的文章中簡單提到過記錄頭信息,在介紹後面的內容前,再詳細看下記錄頭信息中存儲了哪些內容。spa

image.png

行記錄看下來就像下面這樣:翻譯

image.png

  • delete_mask

這個屬性標記當前記錄是否被刪除,值爲1的時候表示記錄被刪除掉了,值爲0的時候表示記錄沒有被刪除。設計

能夠看出,當刪除一條記錄時,只是標記刪除,實際在頁中尚未被移除。這樣作的主要目的是,之後若是有新紀錄插入表中,能夠複用這些已刪除記錄的存儲空間。3d

  • min_rec_mask

B+樹的每層非葉子節點中的最小記錄都會添加該標記,並設置爲1,不然爲0。日誌

看下圖的索引結構,最底層的葉子節點是存放真實數據的,因此每條記錄的 min_rec_mask 都爲 0。上面兩層是非葉子節點,那麼每一個頁中最左邊的最小記錄的 min_rec_mask 就會設置爲 1。

image.png

  • n_owned

表示當前記錄擁有的記錄數,頁中的數據其實還會分爲多個組,每一個組會有一個最大的記錄,最大記錄的 n_owned 就記錄了這個組中的記錄數。在後面介紹 Page Directory 時會看到這個屬性的用途。

  • heap_no

這個屬性表示當前記錄在本頁中的位置。

  • record_type

記錄類型:0 表示普通記錄,1 表示B+樹非葉子節點記錄,2 表示最小記錄,3 表示最大記錄,1xx 表示保留

仍是之前面索引結構圖來看,上面兩層的非葉子節點中的記錄 record_type 都應該爲 1。最底層的葉子節點應該就是普通記錄,record_type 爲 0。其實每一個頁還會有一個最小記錄和最大記錄,record_type 分別爲 2 和 3,這個最小記錄和最大記錄其實就是後面要說的 Infimum 和 Supremum。

  • next_record

表示從當前記錄的真實數據到下一條記錄的真實數據的地址偏移量,若是沒有下一條記錄就是 0。

數據頁中的記錄看起來就像下圖這樣,按主鍵順序排列後,heap_no 記錄了當前記錄在本頁的位置,而後經過 next_record 鏈接起來。

image.png

注意 next_record 指向的是記錄頭與數據之間的位置偏移量。這個位置向左讀取就是記錄頭信息,向右讀取就是真實數據,並且以前說過變長字段長度列表NULL值列表中都是按列逆序存放的,這時往左讀取的標識和往右讀取的列就對應上了,提升了讀取的效率。

若是刪除了其中一條記錄,delete_mask 就設置爲 1,標記爲已刪除,next_record 就會設置爲 0。其實頁中被刪除的記錄會經過 next_record 造成一個垃圾鏈表,供之後插入記錄時重用空間。

image.png

File Header

File Header 用來記錄頁的一些頭信息,由8個部分組成,固定佔用38字節

image.png

主要先看下以下的一些信息:

  • FIL_PAGE_SPACE_OR_CHKSUM

這個表明當前頁面的校驗和(checksum),每當一個頁面在內存中修改了,在同步以前就要把它的校驗和算出來。在一個頁面被刷到磁盤的時候,首先被寫入磁盤的就是這個 checksum。

  • FIL_PAGE_OFFSET

每個頁都有一個單獨的頁號,InnoDB 經過頁號來惟必定位一個頁。

如某獨立表空間 a.ibd 的大小爲1GB,頁的大小默認爲16KB,那麼總共有65536個頁。FIL_PAGE_OFFSET 表示該頁在全部頁中的位置。若此表空間的ID爲10,那麼搜索頁(10,1)就表示查找表a中的第二個頁。

  • FIL_PAGE_PREVFIL_PAGE_NEXT

InnoDB 是以頁爲單位存放數據的,InnoDB 表是索引組織的表,數據是按主鍵順序存放的。數據可能會分散到多個不連續的頁中存儲,這時就會經過 FIL_PAGE_PREV 和 FIL_PAGE_NEXT 將上一頁和下一頁連起來,就造成了一個雙向鏈表。這樣就經過一個雙向鏈表把許許多多的頁就都串聯起來了,而無需這些頁在物理上真正連着。

image.png

  • FIL_PAGE_TYPE

這個表明當前頁的類型,InnoDB 爲了避免同的目的而設計了許多種不一樣類型的頁。

InnoDB 有以下的一些頁類型:

image.png

Page Header

Page Header 用來記錄數據頁的狀態信息,由14個部分組成,共佔用56字節

image.png

  • PAGE_N_DIR_SLOTS

頁中的記錄會按主鍵順序分爲多個組,每一個組會對應到一個槽(Slot),PAGE_N_DIR_SLOTS 就記錄了 Page Directory 中槽的數量。

  • PAGE_HEAP_TOP

PAGE_HEAP_TOP 記錄了 Free Space 的地址,這樣就能夠快速從 Free Space 分配空間到 User Records 了。

  • PAGE_N_HEAP

本頁中的記錄的數量,包括最小記錄(Infimum)和最大記錄(Supremum)以及標記爲刪除(delete_mask=1)的記錄。

  • PAGE_FREE

已刪除的記錄會經過 next_record連成一個單鏈表,這個單鏈表中的記錄空間能夠被從新利用,PAGE_FREE 指向第一個標記爲刪除的記錄地址,就是單鏈表的頭節點。

  • PAGE_GARBAGE

標記爲已刪除的記錄佔用的總字節數。

  • PAGE_N_RECS

本頁中記錄的數量,不包括最小記錄和最大記錄以及被標記爲刪除的記錄,注意和 PAGE_N_HEAP 的區別。

Infimum 和 Supremum

InnoDB 每一個數據頁中有兩個虛擬的行記錄,用來限定記錄的邊界。Infimum記錄是比該頁中任何主鍵值都要小的記錄,Supremum記錄 是比改頁中何主鍵值都要大的記錄。這兩個記錄在頁建立時被創建,而且在任何狀況下不會被刪除。

而且因爲這兩條記錄不是咱們本身定義的記錄,因此它們並不存放在頁的User Records部分,他們被單獨放在一個稱爲Infimum + Supremum的部分。

Infimum 和 Supremum 都是由5字節的記錄頭和8字節的一個固定的部分組成,最小記錄的固定部分就是單詞 infimum,最大記錄的固定部分就是單詞 supremum。因爲不存在可變長字段或可爲空的字段,天然就不存在可變長度字段列表和NULL值列表了。

Infimum和Supremum記錄的結構以下圖所示。須要注意,Infimum 記錄頭的 record_type=2,表示最小記錄;Supremum 記錄頭的 record_type=3,表示最大記錄。

image.png

加上 Infimum 和 Supremum 記錄後,頁中的記錄看起來就像下圖的樣子。Infimum 記錄頭的 next_record 指向該頁主鍵最小的記錄,該頁主鍵最大的記錄的 next_record 則指向 Supremum,Infimum 和 Supremum就構成了記錄的邊界。同時注意,記錄頭中 heap_no 的順序, Infimum 和 Supremum 是排在最前面的。

image.png

User Records 和 Free Space

User Records就是實際存儲行記錄的部分,Free Space明顯就是空閒空間。

在一開始生成頁的時候,並無User Records這個部分,每當插入一條記錄,就會從Free Space部分中申請一個記錄大小的空間到User Records部分,當 Free Space 部分的空間用完以後,這個頁也就使用完了。

Page Directory

首先咱們要知道,InnoDB 的數據是索引組織的,B+樹索引自己並不能找到具體的一條記錄,只能找到該記錄所在的頁,頁是存儲數據的最小基本單位。

以下圖,若是咱們要查找 ID=32 的這行數據,經過索引只能定位到第 17 頁。

image.png

定位到頁以後咱們能夠經過最小記錄Infimum的記錄頭的next_record沿着鏈表一直日後找,就能夠找到 ID=32 這條記錄了。

可是能夠想象,沿着鏈表順序查找的性能是很低的。因此,頁中的數據實際上是分爲多個組的,這看起來就造成了一個子目錄,經過子目錄就能縮小查詢的範圍,提升查詢性能了。

Page Directory 翻譯過來就是頁目錄,這部分存放的就是一個個的槽(Slot),頁中的記錄分爲了多個組,槽就存放了每一個組中最大的那條記錄的相對位置(記錄在頁中的相對位置,不是偏移量)。這個組有多少條記錄,就經過最大記錄的記錄頭中的 n_owned 來表示。

對於分組中的記錄數是有規定的:Infimum記錄 所在的分組只能有 1 條記錄,Supremum記錄 所在的分組中的記錄條數只能在 1~8 條之間,中間的其它分組中記錄數只能在是 4~8 條之間。

Page Directory 的生成過程以下:

  • 初始狀況下一個數據頁裏只有InfimumSupremum兩條記錄,它們分屬於兩個組。Page Directory 中就有兩個槽,分別指向這兩條記錄,且這兩條記錄的 n_owned 都等於 1

  • 以後每插入一條記錄,都會從頁目錄中找到主鍵值比本記錄的主鍵值大而且差值最小的槽,而後把該槽對應的記錄的 n_owned 值加1,表示本組內又添加了一條記錄,直到該組中的記錄數等於8條

  • 在一個組中的記錄數等於8條後再插入一條記錄時,會將組中的記錄拆分紅兩個組,一個組中4條記錄,另外一個5條記錄。這個過程會在頁目錄中新增一個槽來記錄這個新增分組中最大的那條記錄的相對位置。

  • 當記錄被刪除時,對應槽的最大記錄的 n_owned 會減 1,當 n_owned 小於 4 時,各分組就會平衡一下,總之要知足上面的規定。

其實正常狀況下,按照主鍵自增加新增記錄,可能每次都是添加到 Supremum 所在的組,直到它的 n_owned 等於8時,再新增記錄時就會分紅兩個組,一個組4條記錄,一個組5條記錄。還會新增一個槽,指向4條記錄分組中的最大記錄,而且這個最大記錄的n_owned會改成4Supremumn_owned就會改成5

Page Directory 中槽(Slot)的數量就會記錄到 Page Header 中的 PAGE_N_DIR_SLOTS

咱們能夠經過下圖來理解下 Page Directory 中槽(Slot)和分組中最大記錄的關係。

  • 首先,Slot0 指向 Infimum 記錄,由於最小記錄所在的分組只能有一條記錄,它的 n_owned=1.
  • 接着 Slot一、Slot二、Slot3 分別指向各自分組中的最大記錄,且 n_owned=4,能夠想象其實就是 Supremum 組分組而來的。
  • 最後,Slot4 指向 Supremum,這是最大記錄的組,通過分組後,它的 n_owned=5

image.png

能夠看到,頁中的數據通過分組後在 Page Directory 中就造成了一個目錄槽,每一個槽就指向了分組中的最大記錄,最大記錄的記錄頭中的 n_owned 就記錄了這個組中的記錄數。

有了目錄槽以後,InnoDB就會利用二叉查找迅速肯定記錄所在的槽,並找到該槽所在分組中主鍵值最小的那條記錄,再經過最小記錄的 next_record 遍歷記錄,就能快速定位到匹配的那條記錄了。

二叉查找的時間複雜度很低,同時在內存中的查找很快,所以一般會忽略這部分查找所用的時間。

File Trailer

前面介紹 File Header 時說過,在將頁寫入磁盤時,最早寫入的即是 File Header 中的 FIL_PAGE_SPACE_OR_CHKSUM 值,就是頁面的校驗和。在寫入的過程當中,數據庫可能發生宕機,致使頁沒有完整的寫入磁盤。

爲了校驗頁是否完整寫入磁盤,InnoDB 就設置了 File Trailer 部分。File Trailer 中只有一個FIL_PAGE_END_LSN,佔用8字節。FIL_PAGE_END_LSN 又分爲兩個部分,前4字節表明頁的校驗和;後4字節表明頁面被最後修改時對應的日誌序列位置(LSN),與File Header中的FIL_PAGE_LSN相同。

默認狀況下,InnoDB存儲引擎每次從磁盤讀取一個頁就會檢測該頁的完整性,這時就會將 File Trailer 中的校驗和、LSN 與 File Header 中的 FIL_PAGE_SPACE_OR_CHKSUMFIL_PAGE_LSN 進行比較,以此來保證頁的完整性。

相關文章
相關標籤/搜索