這篇文章為大家帶來了關於mysql原理中InnoDB資料頁的相關知識,其中包括了頁目錄、頁頭和文件頭部的相關知識,希望對大家有幫助。
它是InnoDB
管理儲存空間的基本單位,一個頁的大小一般是16KB
。 InnoDB
為了不同的目的而設計了許多種不同類型的頁
,例如存放表空間頭部資訊的頁,存放Insert Buffer
資訊的頁,存放INODE
資訊的頁,存放undo
日誌資訊的頁等等等等。當然了,如果我說的這些名詞你一個都沒聽過,就當我放了個屁吧~ 不過這沒有一毛錢關係,我們今兒個也不准備說這些類型的頁,我們聚焦的是那些存放我們表中記錄的那種類型的頁,官方稱這種存放記錄的頁為索引(INDEX
)頁,鑑於我們還沒有了解過索引是個什麼東西,而這些表中的記錄就是我們日常口中所稱的資料
,所以目前還是叫這種存放記錄的頁為資料頁
吧。
資料頁所代表的這塊16KB
大小的儲存空間可以分割成多個部分,不同部分有不同的功能,各個部分如圖所示:
從圖中可以看出,一個InnoDB
資料頁的儲存空間大致被分割成了 7
個部分,有的部分所佔用的位元組數是確定的,有的部分所佔用的位元組數是不確定的。下邊我們用表格的方式來大致描述一下這7個部分都儲存一些啥內容(快速的瞅一眼就行了,後邊會詳細嘮叨的):
名稱 | 中文名稱 | 佔用空間大小 | #簡單描述 |
---|---|---|---|
File Header |
檔案頭 |
38 位元組 |
|
|
|
Page Header | |
56 | 位元組資料頁專有的一些資訊 | |
Infimum Supremum |
26 | 位元組兩個虛擬的行記錄 | ||
使用者記錄 | 不確定實際儲存的行記錄內容 | ||
空閒空間 | 不確定頁中尚未使用的空間 | ||
頁面目錄 | #不確定#頁中的某些記錄的相對位置 |
|
File Trailer |
記錄在頁中的儲存
在頁的7個組成部分中,我們自己儲存的記錄會依照我們指定的行格式
儲存到User Records
部分。但在一開始產生頁面的時候,其實並沒有User Records
這個部分,每當我們插入一筆記錄,都會從Free Space
部分,也就是尚未使用的儲存空間中申請一個記錄大小的空間分割到User Records
部分,當Free Space
部分的空間全部被User Records
部分取代掉之後,也就代表這個頁使用完了,如果還有新的記錄插入的話,就需要去申請新的頁了,這個過程的圖示如下:
為了更好的管理在User Records
中的這些記錄,InnoDB
可費了一番力氣呢,在哪費力氣了呢?不就是把記錄依照指定的行格式一一擺在User Records
部分麼?其實這話還得從記錄行格式的記錄頭資訊
說起。
為了故事的順利發展,我們先建立一個表:
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)
這個新建立的page_demo
表有3個列,其中c1
和c2
列是用來儲存整數的,c3
列是用來儲存字串的。要注意的是,我們把 c1 列指定為主鍵,所以在特定的行格式中InnoDB就沒必要為我們去創建那個所謂的 #row_id 隱藏列了。而且我們為這個表指定了ascii
字元集以及Compact
的行格式。所以這個表中記錄的行格式示意圖就是這樣的:
從圖中可以看到,我們特意把記錄頭資訊
的5個位元組的資料給標出來了,表示它很重要,我們再次先把這些記錄頭資訊
中各個屬性的大體意思瀏覽一下(我們目前使用Compact
行格式進行示範):
名稱 | 大小(單位:bit) | 描述 |
---|---|---|
預留位元1 |
1 |
#沒有使用 |
預留位元2 |
1 |
沒有使用 |
##delete_mask
|
1
| 標記該記錄是否已刪除|
#min_rec_mask
|
|
|
|
|
##1 |
B 樹的每層非葉子節點中的最小記錄都會加入該標記
|
n_owned | 4 |
表示目前記錄擁有的記錄數
|
#heap_no |
# #13
表示目前記錄在記錄堆的位置資訊
record_type |
表示目前記錄的類型, | 0表示普通記錄,1 表示B 樹非葉節點記錄, | 2表示最小記錄, | 3
由于我们现在主要在唠叨记录头信息
的作用,所以为了大家理解上的方便,我们只在page_demo
表的行格式演示图中画出有关的头信息属性以及c1
、c2
、c3
列的信息(其他信息没画不代表它们不存在啊,只是为了理解上的方便在图中省略了~),简化后的行格式示意图就是这样:
下边我们试着向page_demo
表中插入几条记录:
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
为了方便大家分析这些记录在页
的User Records
部分中是怎么表示的,我把记录中头信息和实际的列数据都用十进制表示出来了(其实是一堆二进制位),所以这些记录的示意图就是:
看这个图的时候需要注意一下,各条记录在User Records
中存储的时候并没有空隙,这里只是为了大家观看方便才把每条记录单独画在一行中。我们对照着这个图来看看记录头信息中的各个属性是啥意思:
delete_mask
这个属性标记着当前记录是否被删除,占用1个二进制位,值为0
的时候代表记录并没有被删除,为1
的时候代表记录被删除掉了。
啥?被删除的记录还在页
中么?是的,摆在台面上的和背地里做的可能大相径庭,你以为它删除了,可它还在真实的磁盘上[摊手](忽然想起冠希~)。这些被删除的记录之所以不立即从磁盘上移除,是因为移除它们之后把其他的记录在磁盘上重新排列需要性能消耗,所以只是打一个删除标记而已,所有被删除掉的记录都会组成一个所谓的垃圾链表
,在这个链表中的记录占用的空间称之为所谓的可重用空间
,之后如果有新记录插入到表中的话,可能把这些被删除的记录占用的存储空间覆盖掉。
min_rec_mask
B+树的每层非叶子节点中的最小记录都会添加该标记,什么是个B+
树?什么是个非叶子节点?好吧,等会再聊这个问题。反正我们自己插入的四条记录的min_rec_mask
值都是0
,意味着它们都不是B+
树的非叶子节点中的最小记录。
n_owned
这个暂时保密,稍后它是主角~
heap_no
这个属性表示当前记录在本页
中的位置,从图中可以看出来,我们插入的4条记录在本页
中的位置分别是:2
、3
、4
、5
。是不是少了点啥?是的,怎么不见heap_no
值为0
和1
的记录呢?
这其实是设计InnoDB
的大叔们玩的一个小把戏,他们自动给每个页里边儿加了两个记录,由于这两个记录并不是我们自己插入的,所以有时候也称为伪记录
或者虚拟记录
。这两个伪记录一个代表最小记录
,一个代表最大记录
,等一下哈~,记录可以比大小么?
是的,记录也可以比大小,对于一条完整的记录来说,比较记录的大小就是比较主键
的大小。比方说我们插入的4行记录的主键值分别是:1
、2
、3
、4
,这也就意味着这4条记录的大小从小到大依次递增。
但是不管我们向页
中插入了多少自己的记录,设计InnoDB
的大叔们都规定他们定义的两条伪记录分别为最小记录与最大记录。这两条记录的构造十分简单,都是由5字节大小的记录头信息
和8字节大小的一个固定的部分组成的,如图所示
由于这两条记录不是我们自己定义的记录,所以它们并不存放在页
的User Records
部分,他们被单独放在一个称为Infimum + Supremum
的部分,如图所示:
从图中我们可以看出来,最小记录和最大记录的heap_no
值分别是0
和1
,也就是说它们的位置最靠前。
record_type
这个属性表示当前记录的类型,一共有4种类型的记录,0
表示普通记录,1
表示B+树非叶节点记录,2
表示最小记录,3
表示最大记录。从图中我们也可以看出来,我们自己插入的记录就是普通记录,它们的record_type
值都是0
,而最小记录和最大记录的record_type
值分别为2
和3
。
至于record_type
为1
的情况,我们之后在说索引的时候会重点强调的。
next_record
这玩意儿非常重要,它表示从当前记录的真实数据到下一条记录的真实数据的地址偏移量。比方说第一条记录的next_record
值为32
,意味着从第一条记录的真实数据的地址处向后找32
个字节便是下一条记录的真实数据。如果你熟悉数据结构的话,就立即明白了,这其实是个链表
,可以通过一条记录找到它的下一条记录。但是需要注意注意再注意的一点是,下一条记录
指得并不是按照我们插入顺序的下一条记录,而是按照主键值由小到大的顺序的下一条记录。而且规定 Infimum记录(也就是最小记录) 的下一条记录就是本页中主键值最小的用户记录,而本页中主键值最大的用户记录的下一条记录就是 Supremum记录(也就是最大记录) ,为了更形象的表示一下这个next_record
起到的作用,我们用箭头来替代一下next_record
中的地址偏移量:
从图中可以看出来,我们的记录按照主键从小到大的顺序形成了一个单链表。最大记录
的next_record
的值为0
,这也就是说最大记录是没有下一条记录
了,它是这个单链表中的最后一个节点。如果从中删除掉一条记录,这个链表也是会跟着变化的,比如我们把第2条记录删掉:
mysql> DELETE FROM page_demo WHERE c1 = 2; Query OK, 1 row affected (0.02 sec)
删掉第2条记录后的示意图就是:
从图中可以看出来,删除第2条记录前后主要发生了这些变化:
delete_mask
值设置为1
。next_record
值变为了0,意味着该记录没有下一条记录了。next_record
指向了第3条记录。最大记录
的n_owned
值从5
变成了4
,关于这一点的变化我们稍后会详细说明的。所以,不论我们怎么对页中的记录做增删改操作,InnoDB始终会维护一条记录的单链表,链表中的各个节点是按照主键值由小到大的顺序连接起来的。
再来看一个有意思的事情,因为主键值为2
的记录被我们删掉了,但是存储空间却没有回收,如果我们再次把这条记录插入到表中,会发生什么事呢?
mysql> INSERT INTO page_demo VALUES(2, 200, 'bbbb'); Query OK, 1 row affected (0.00 sec)
我们看一下记录的存储情况:
从图中可以看到,InnoDB
并没有因为新记录的插入而为它申请新的存储空间,而是直接复用了原来被删除记录的存储空间。
现在我们了解了记录在页中按照主键值由小到大顺序串联成一个单链表,那如果我们想根据主键值查找页中的某条记录该咋办呢?比如说这样的查询语句:
SELECT * FROM page_demo WHERE c1 = 3;
最笨的办法:从Infimum
记录(最小记录)开始,沿着链表一直往后找,总有一天会找到(或者找不到[摊手]),在找的时候还能投机取巧,因为链表中各个记录的值是按照从小到大顺序排列的,所以当链表的某个节点代表的记录的主键值大于你想要查找的主键值时,你就可以停止查找了,因为该节点后边的节点的主键值依次递增。
這個方法在頁中儲存的記錄數量比較少的情況用起來也沒啥問題,比方說現在我們的表裡只有4
條自己插入的記錄,所以最多找 4
次就可以把所有記錄都遍歷一遍,但是如果一個頁中儲存了非常多的記錄,那麼查找對效能來說還是有損耗的,所以我們說這種遍歷查找這是一個笨
辦法。但是設計InnoDB
的大叔們是什麼人,他們能用這麼笨的辦法麼,當然是要設計一種更6的查找方式嘍,他們從書的目錄中找到了靈感。
我們平常想從一本書中查找某個內容的時候,一般會先看目錄,找到需要查找的內容對應的書的頁碼,然後到對應的頁碼查看內容。設計InnoDB
的大叔們也為我們的記錄製作了一個類似的目錄,他們的製作過程是這樣的:
將所有正常的記錄(包括最大和最小記錄,不包括標記為已刪除的記錄)劃分為幾個群組。
每個群組的最後一筆記錄(也就是群組內最大的那筆記錄)的頭資訊中的n_owned
屬性表示該記錄擁有多少筆記錄,也就是該組內共有幾筆記錄。
將每個群組的最後一筆記錄的位址偏移量單獨提取出來按順序儲存到靠近頁
的尾部的地方,這個地方就是所謂的Page Directory
,也就是頁目錄
(此時應該回傳頭看看頁面各部分的圖)。頁面目錄中的這些位址偏移稱為插槽
(英文名稱:Slot
),所以這個頁面目錄就是由插槽
組成的。
比方說現在的page_demo
表中正常的記錄共有6條,InnoDB
會把它們分成兩組,第一組中只有一個最小記錄,第二組中是剩餘的5筆記錄,看下邊的示意圖:
#從這個圖中我們需要注意這麼幾點:
現在頁目錄
部分中有兩個槽,也就表示我們的記錄被分成了兩個群組,槽1
中的值是112
,代表最大記錄的位址偏移(就是從頁面的0位元組開始數,數112個位元組);槽0
中的值是 99
,代表最小記錄的位址偏移。
注意最小和最大記錄的頭資訊中的n_owned
#屬性
n_owned
值為1
,這就代表著以最小記錄結尾的這個分組中只有1
筆記錄,也就是最小記錄本身。 n_owned
值為5
,這就代表著以最大記錄結尾的這個分組中只有5
筆記錄,包括最大記錄本身還有我們自己插入的4
筆記錄。 99
和112
這樣的位址偏移量很不直觀,我們用箭頭指向的方式取代數字,這樣更容易我們理解,所以修改後的示意圖就是這樣:
哎呀,咋看上去怪怪的,這麼亂的圖對於我這個強迫症真是不能忍,那我們就暫時不管各筆記錄在儲存裝置上的排列方式了,單純從邏輯上看一下這些記錄和頁目錄的關係:
##這樣看就順眼多了嘛!為什麼最小記錄的n_owned值為1,而最大記錄的
n_owned#值為
5呢,這裡頭有什麼貓膩麼?
InnoDB的大叔們對每個分組中的記錄條數是有規定的:對於最小記錄所在的分組只能有
1 筆記錄,最大記錄所在的分組擁有的記錄條數只能在 #1~8 條之間,剩餘的分組中記錄的條數範圍只能在是 4~8 條之間。所以分組是按照下邊的步驟進行的:
頁目錄中找到主鍵值比本記錄的主鍵值大且差值最小的槽,然後把此槽對應的記錄的
n_owned值加1,表示本組內又增加了一筆記錄,直到該組中的記錄數等於8個。
在一个组中的记录数等于8个后再插入一条记录时,会将组中的记录拆分成两个组,一个组中4条记录,另一个5条记录。这个过程会在页目录
中新增一个槽
来记录这个新增分组中最大的那条记录的偏移量。
由于现在page_demo
表中的记录太少,无法演示添加了页目录
之后加快查找速度的过程,所以再往page_demo
表中添加一些记录:
mysql> INSERT INTO page_demo VALUES(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'); Query OK, 12 rows affected (0.00 sec) Records: 12 Duplicates: 0 Warnings: 0
哈,我们一口气又往表中添加了12条记录,现在页里边就一共有18条记录了(包括最小和最大记录),这些记录被分成了5个组,如图所示:
因为把16条记录的全部信息都画在一张图里太占地方,让人眼花缭乱的,所以只保留了用户记录头信息中的n_owned
和next_record
属性,也省略了各个记录之间的箭头,我没画不等于没有啊!现在看怎么从这个页目录
中查找记录。因为各个槽代表的记录的主键值都是从小到大排序的,所以我们可以使用所谓的二分法
来进行快速查找。5个槽的编号分别是:0
、1
、2
、3
、4
,所以初始情况下最低的槽就是low=0
,最高的槽就是high=4
。比方说我们想找主键值为6
的记录,过程是这样的:
计算中间槽的位置:(0+4)/2=2
,所以查看槽2
对应记录的主键值为8
,又因为8 > 6
,所以设置high=2
,low
保持不变。
重新计算中间槽的位置:(0+2)/2=1
,所以查看槽1
对应的主键值为4
,又因为4 ,所以设置<code>low=1
,high
保持不变。
因为high - low
的值为1,所以确定主键值为6
的记录在槽2
对应的组中。此刻我们需要找到槽2
中主键值最小的那条记录,然后沿着单向链表遍历槽2
中的记录。但是我们前边又说过,每个槽对应的记录都是该组中主键值最大的记录,这里槽2
对应的记录是主键值为8
的记录,怎么定位一个组中最小的记录呢?别忘了各个槽都是挨着的,我们可以很轻易的拿到槽1
对应的记录(主键值为4
),该条记录的下一条记录就是槽2
中主键值最小的记录,该记录的主键值为5
。所以我们可以从这条主键值为5
的记录出发,遍历槽2
中的各条记录,直到找到主键值为6
的那条记录即可。由于一个组中包含的记录条数只能是1~8条,所以遍历一个组中的记录的代价是很小的。
所以在一个数据页中查找指定主键值的记录的过程分为两步:
通过二分法确定该记录所在的槽,并找到该槽所在分组中主键值最小的那条记录。
通过记录的next_record
属性遍历该槽所在的组中的各个记录。
设计InnoDB
的大叔们为了能得到一个数据页中存储的记录的状态信息,比如本页中已经存储了多少条记录,第一条记录的地址是什么,页目录中存储了多少个槽等等,特意在页中定义了一个叫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 |
|
已刪除已刪除記錄所佔用的位元組數 |
|
|
|
|
|
|
PAGE_LAST_INSERT |
2 |
#最後插入記錄的位置
|
##PAGE_DIRECTION
|
2 | 位元組
|
#PAGE_N_DIRECTION
| 2 | 位元組
|
PAGE_N_RECS
|
2 | 位元組
|
#PAGE_MAX_TRX_ID
|
8 | 位元組
|
PAGE_LEVEL
|
2 | 位元組
如果大家認真看過前邊的文章,從PAGE_N_DIR_SLOTS
到PAGE_LAST_INSERT
以及PAGE_N_RECS
的意思大家一定是清楚的,如果不清楚,對不起,你應該回頭再看一次前邊的文章。剩下的狀態資訊看不明白不要著急,飯要一口一口吃,東西要一點一點學(一定要稍安勿躁哦,不要被這些名詞嚇到)。這裡我們先嘮叨一下PAGE_DIRECTION
和PAGE_N_DIRECTION
的意思:
##PAGE_DIRECTION
PAGE_DIRECTION。
PAGE_N_DIRECTION
InnoDB會把沿著同一個方向插入記錄的條數記下來,這個條數就用
PAGE_N_DIRECTION這個狀態表示。當然,如果最後一筆記錄的插入方向改變了的話,這個狀態的值會被清除重新統計。
Page Header是專門針對
資料頁記錄的各種狀態信息,比方說頁裡頭有多少個記錄了呀,有多少個槽了呀。我們現在描述的
File Header針對各種類型的頁都通用,也就是說不同類型的頁都會以
File Header作為第一個組成部分,它描述了一些針對各種頁都通用的一些信息,比方說這個頁的編號是多少,它的上一個頁、下一個頁是誰啦吧啦吧啦~ 這個部分佔用固定的
38個字節,是由下邊這些內容組成的:
#佔用空間大小 | 描述 | |||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
#FIL_PAGE_SPACE_OR_CHKSUM
|
4位元組
| 頁的校驗和(checksum值)|||||||||||||||||||||||||||||||||||||
FIL_PAGE_OFFSET
|
4位元組
| 頁號|||||||||||||||||||||||||||||||||||||
#FIL_PAGE_PREV
|
4位元組
| 上一個頁的頁號|||||||||||||||||||||||||||||||||||||
FIL_PAGE_NEXT
|
4位元組
| 下一個頁的頁號|||||||||||||||||||||||||||||||||||||
| 8位元組 頁面被最後修改時對應的日誌序列位置(英文名稱是:Log Sequence Number) |
|||||||||||||||||||||||||||||||||||||
| 2位元組 該頁的型別 |
|||||||||||||||||||||||||||||||||||||
8 | 位元組僅在系統表空間的一個頁中定義,代表檔案至少被刷新到了對應的LSN值 | |||||||||||||||||||||||||||||||||||||
#4 | 位元組頁屬於哪個表空間 |
# |
類型名稱 | 十六進位 | 描述 |
---|---|---|
#FIL_PAGE_TYPE_ALLOCATED |
0x0000 | 最新分配,還沒使用 |
FIL_PAGE_UNDO_LOG |
##FIL_PAGE_UNDO_LOG | |
Undo日誌頁 |
FIL_PAGE_INODE | |
段落資訊節點 | FIL_PAGE_IBUF_FREE_LIST | |
Insert Buffer空閒清單 |
FIL_PAGE_IBUF_BITMAP | |
##0x0005 |
Insert Buffer位圖 | |
0x0006 |
系統頁 | |
#0x0007 |
#交易系統資料 | |
|
||
|
# FIL_PAGE_TYPE_FSP_HDR | |
表空間頭部資訊 |
FIL_PAGE_TYPE_XDES
|
我們存放記錄的資料頁的型別其實是FIL_PAGE_INDEX
,也就是所謂的索引頁
。至於啥是索引,而聽下回分解~
FIL_PAGE_PREV
和FIL_PAGE_NEXT
#我們前邊強調過,InnoDB
都是以頁為單位存放資料的,有時候我們存放某種類型的資料佔用的空間非常大(比方說一張表中可以有成千上萬筆記錄),InnoDB
可能不可以一次為這麼多資料分配一個非常大的儲存空間,如果分散到多個不連續的頁中儲存的話需要把這些頁關聯起來,FIL_PAGE_PREV
和FIL_PAGE_NEXT
就分別代表本頁的上一個和下一個頁的頁號。這樣透過建立一個雙向鍊錶把許許多多的頁就都串聯起來了,而無需這些頁在物理上真正連著。要注意的是,並不是所有類型的頁都有上一個和下一個頁的屬性,不過我們本集中嘮叨的資料頁
(也就是類型為FIL_PAGE_INDEX
的頁)是有這兩個屬性的,所以所有的資料頁其實是雙鍊錶,就像這樣:
關於 File Header
的其他屬性我們暫時用不到,等用到的時候再提哈~
我們知道InnoDB
儲存引擎會把資料儲存到磁碟上,但是磁碟速度太慢,需要以頁
為單位把資料載入到記憶體中處理,如果該頁中的資料在記憶體中被修改了,那麼在修改後的某個時間需要把資料同步到磁碟。但是在同步了一半的時候中斷電了咋辦,這不是莫名尷尬麼?為了偵測一個頁面是否完整(也就是在同步的時候有沒有發生只同步一半的尷尬情況),設計InnoDB
的大叔們在每個頁的尾部都加了一個File Trailer
部分,這個部分由8
個位元組組成,可以分成2個小部分:
前4個位元組代表頁的校驗和
這個部分是和File Header
中的校驗和相對應的。每當一個頁面在記憶體中修改了,在同步之前就要把它的校驗和算出來,因為File Header
在頁面的前邊,所以校驗和會被先同步到磁碟,當完全寫完時,校驗和也會被寫到頁的尾部,如果完全同步成功,則頁的首部和尾部的校驗和應該是一致的。如果寫了一半兒斷電了,那麼在File Header
中的校驗和就代表著已經修改過的頁,而在File Trailer
中的校驗和代表著原先的頁,二者不同則表示同步中間出錯。
後4個位元組代表頁面被最後修改時對應的日誌序列位置(LSN)
這個部分也是為了校驗頁的完整性的,只不過我們目前還沒說LSN
是個什麼意思,所以大家可以先不用管這個屬性。
這個File Trailer
與File Header
類似,都是所有類型的頁通用的。
InnoDB為了不同的目的而設計了不同類型的頁,我們把用來存放記錄的頁叫做資料頁
。
一個資料頁可以大致分割為7個部分,分別是
File Header
,表示頁的一些通用訊息,佔固定的38位元組。 Page Header
,表示資料頁專有的一些訊息,佔固定的56個位元組。 Infimum Supremum
,兩個虛擬的偽記錄,分別表示頁中的最小和最大記錄,佔固定的26
個位元組。 User Records
:真實儲存我們插入的記錄的部分,大小不固定。 Free Space
:頁中尚未使用的部分,大小不確定。 Page Directory
:頁中的某些記錄相對位置,也就是各個槽在頁面中的位址偏移量,大小不固定,插入的記錄越多,這個部分佔用的空間越多。 File Trailer
:用來檢驗頁是否完整的部分,佔用固定的8個位元組。 每個記錄的頭資訊中都有一個next_record
屬性,從而使頁中的所有記錄串連成一個單鍊錶
。
InnoDB
會把頁中的記錄分成若干個群組,每個群組的最後一個記錄的位址偏移量當作一個槽
,存放在Page Directory
中,所以在一個頁中根據主鍵查找記錄是非常快的,分為兩個步驟:
每個資料頁的File Header
部分都有上一個和下一個頁的編號,所以所有的資料頁會組成一個雙鍊錶
。
為確保從記憶體中同步到磁碟的頁的完整性,在頁的首部和尾部都會儲存頁中資料的校驗和和頁面最後修改時對應的LSN
值,如果首部和尾部的校驗和和LSN
值校驗不成功的話,就表示同步過程出現了問題。
推薦學習:mysql影片教學
以上是深入研究MySQL原理篇之InnoDB資料頁的詳細內容。更多資訊請關注PHP中文網其他相關文章!