數據頁結構的快速瀏覽數據頁表明的這塊16KB大小的存儲空間能夠被劃分爲多個部分,不一樣部分有不一樣的功能,各個部分如圖所示:mysql
一個InnoDB數據頁的存儲空間大體被劃分紅了7個部分算法
1、記錄在頁中的存放
一、記錄頭信息的祕密sql
咱們先建立一個表:ide
mysql> CREATE TABLE page_demo( -> c1 INT, -> c2 INT, -> c3 VARCHAR(10000), -> PRIMARY KEY (c1) -> ) CHARSET=ascii ROW_FORMAT=Compact; Query OK, 0 rows affected (0.03 sec)
c1和c2列是用來存儲整數的,c3列是用來存儲字符串的spa
在page_demo表的行格式演示圖中畫出有關的頭信息屬性以及c一、c二、c3列的信息(其餘信息沒畫不表明它們不存在啊,只是爲了理解上的方便在圖中省略了~),簡化後的行格式示意圖就是這樣:3d
繼續插入數據日誌
mysql> INSERT INTO page_demo VALUES(1, 100, 'aaaa'), (2, 200, 'bbbb'), (3, 300, 'cccc'), (4, 400, 'dddd'); Query OK, 4 rows affected (0.00 sec) Records: 4 Duplicates: 0 Warnings: 0
如圖所示code
因爲這兩條記錄不是咱們本身定義的記錄,因此它們並不存放在頁的User Records部分,他們被單獨放在一個稱爲Infimum + Supremum的部分,如圖所示:blog
其表明的 heap_no 分別是 0 和 1索引
record_type 這個屬性表示當前記錄的類型,一共有4種類型的記錄,0表示普通記錄,1表示B+樹葉節點記錄,2表示最小記錄,3表示最大記錄。從圖中咱們也能夠看出來,咱們本身插入的記錄就是普通記錄,它們的record_type值都是0,而最小記錄和最大記錄的record_type值分別爲2和3
next_record 它表示從當前記錄的真實數據到下一條記錄的真實數據的地址偏移量 最大記錄的next_record的值爲0
若是從中刪除掉一條記錄,這個鏈表也是會跟着變化的,好比咱們把第2條記錄刪掉:
從圖中能夠看出來,刪除第2條記錄先後主要發生了這些變化: 第2條記錄並無從存儲空間中移除,而是把該條記錄的delete_mask值設置爲1。 第2條記錄的next_record值變爲了0,意味着該記錄沒有下一條記錄了。 第1條記錄的next_record指向了第3條記錄。 還有一點你可能忽略了,就是最大記錄的n_owned值從5變成了4,關於這一點的變化咱們稍後會詳細說明的。
若是咱們再次把這條記錄插入到表中的話
從圖中能夠看到,InnoDB並無由於新記錄的插入而爲它申請新的存儲空間,而是直接複用了原來被刪除記錄的存儲空間。
2、Page Directory(頁目錄)如今咱們瞭解了記錄在頁中按照主鍵值由小到大順序串聯成一個單鏈表,那若是咱們想根據主鍵值查找頁中的某條記錄該咋辦呢
SELECT * FROM page_demo WHERE c1 = 3;
MySQL 的查詢流程 將全部正常的記錄(包括最大和最小的記錄,不包括標記爲已刪除的記錄)劃分爲幾個組。 每一個組的最後一條記錄(也就是組內最大的那條記錄)的頭信息中的n_owned屬性表示該記錄擁有多少條記錄,也就是該組內共有幾條記錄。 將每一個組的最後一條記錄的地址偏移量單獨提取出來按順序存儲到靠近頁的尾部的地方,這個地方就是所謂的Page Directory,也就是頁目錄(此時應該返回頭看看頁面各個部分地圖)。頁面目錄中的這些地址偏移量被稱爲槽(英文名:Slot),因此這個頁面目錄就是由槽組成的。
比方說如今的page_demo表中正常的記錄共有6條,InnoDB會把它們分紅兩組,第一組中只有一個最小記錄,第二組中是剩餘的5條記錄,看下邊的示意圖:
注意最小和最大記錄的頭信息中的n_owned屬性 最小記錄的n_owned值爲1,這就表明着以最小記錄結尾的這個分組中只有1條記錄,也就是最小記錄自己。 最大記錄的n_owned值爲5,這就表明着以最大記錄結尾的這個分組中只有5條記錄,包括最大記錄自己還有咱們本身插入的4條記錄。
對於最小記錄所在的分組只能有 1 條記錄,最大記錄所在的分組擁有的記錄條數只能在 1~8 條件之間,剩下的分組中記錄的條數範圍只能在是 4~8 條之間。
因此 最小記錄的 n_owned 是 1, 當前最大記錄的 n_owned 是 5
以後每插入一條記錄,都會從頁目錄中找到主鍵值比本記錄的主鍵值大而且差值最小的槽,而後把該槽對應的記錄的n_owned值加1,表示本組內又添加了一條記錄,直到該組中的記錄數等於8個。
在一個組中的記錄數等於8個後再插入一條記錄時,會將組中的記錄拆分紅兩個組,一個組中4條記錄,另外一個5條記錄。這個過程會在頁目錄中新增一個槽來記錄這個新增分組中最大的那條記錄的偏移量。
再次添加12條數據
比方說咱們想找主鍵值爲6的記錄
因此在一個數據頁中查找指定主鍵值的記錄的過程分爲兩步:
爲了能獲得一個數據頁中存儲的記錄的狀態信息,好比本頁中已經存儲了多少條記錄,第一條記錄的地址是什麼,頁目錄中存儲了多少個槽等等,特地在頁中定義了一個叫Page Header的部分,它是頁結構的第二部分,這個部分佔用固定的56個字節,專門存儲各類狀態信息,具體各個字節都是幹嗎的看下錶:
名稱 | 佔用空間大小 | 描述 |
---|---|---|
PAGE_N_DIR_SLOTS | 2字節 | 在頁目錄中的槽數量 |
PAGE_HEAP_TOP | 2字節 | 還未使用的空間最小地址,也就是說從該地址以後就是Free Space |
PAGE_N_HEAP | 2字節 | 本頁中的記錄的數量(包括最小和最大記錄以及標記爲刪除的記錄) |
PAGE_FREE | 2字節 | 第一個已經標記爲刪除的記錄地址(各個已刪除的記錄經過next_record也會組成一個單鏈表,這個單鏈表中的記錄能夠被從新利用) |
PAGE_GARBAGE | 2字節 | 已刪除記錄佔用的字節數 |
PAGE_LAST_INSERT | 2字節 | 最後插入記錄的位置 |
PAGE_DIRECTION | 2字節 | 記錄插入的方向 |
PAGE_N_DIRECTION | 2字節 | 一個方向連續插入的記錄數量 |
PAGE_N_RECS | 2字節 | 該頁中記錄的數量(不包括最小和最大記錄以及被標記爲刪除的記錄) |
PAGE_MAX_TRX_ID | 8字節 | 修改當前頁的最大事務ID,該值僅在二級索引中定義 |
PAGE_LEVEL | 2字節 | 當前頁在B+樹中所處的層級 |
PAGE_INDEX_ID | 8字節 | 索引ID,表示當前頁屬於哪一個索引 |
PAGE_BTR_SEG_LEAF | 10字節 | B+樹葉子段的頭部信息,僅在B+樹的Root頁定義 |
PAGE_BTR_SEG_TOP | 10字節 | B+樹非葉子段的頭部信息,僅在B+樹的Root頁定義 |
存儲頁的通用信息,比方說這個頁的編號是多少,它的上一個頁、下一個頁是誰啦吧啦吧啦~ 這個部分佔用固定的38個字節,是由下邊這些內容組成的:
名稱 | 佔用空間大小 | 描述 |
---|---|---|
FIL_PAGE_SPACE_OR_CHKSUM | 4字節 | 頁的校驗和(checksum值) |
FIL_PAGE_OFFSET | 4字節 | 頁號 |
FIL_PAGE_PREV | 4字節 | 上一個頁的頁號 |
FIL_PAGE_NEXT | 4字節 | 下一個頁的頁號 |
FIL_PAGE_LSN | 8字節 | 頁面被最後修改時對應的日誌序列位置(英文名是:Log Sequence Number) |
FIL_PAGE_TYPE | 2字節 | 該頁的類型 |
FIL_PAGE_FILE_FLUSH_LSN | 8字節 | 僅在系統表空間的一個頁中定義,表明文件至少被刷新到了對應的LSN值 |
FIL_PAGE_ARCH_LOG_NO_OR_SPACE_ID | 4字節 | 頁屬於哪一個表空間 |
對照着這個表格,咱們看幾個目前比較重要的部分:
5、總結一個數據頁能夠被大體劃分爲7個部分,分別是 File Header,表示頁的一些通用信息,佔固定的38字節。 Page Header,表示數據頁專有的一些信息,佔固定的56個字節。 Infimum + Supremum,兩個虛擬的僞記錄,分別表示頁中的最小和最大記錄,佔固定的26個字節。 User Records:真實存儲咱們插入的記錄的部分,大小不固定。 Free Space:頁中還沒有使用的部分,大小不肯定。 Page Directory:頁中的某些記錄相對位置,也就是各個槽在頁面中的地址偏移量,大小不固定,插入的記錄越多,這個部分佔用的空間越多。 File Trailer:用於檢驗頁是否完整的部分,佔用固定的8個字節。