首頁  >  文章  >  資料庫  >  深入研究MySQL原理篇之InnoDB資料頁

深入研究MySQL原理篇之InnoDB資料頁

WBOY
WBOY轉載
2022-01-19 17:28:571787瀏覽

這篇文章為大家帶來了關於mysql原理中InnoDB資料頁的相關知識,其中包括了頁目錄、頁頭和文件頭部的相關知識,希望對大家有幫助。

深入研究MySQL原理篇之InnoDB資料頁

不同類型的頁簡介

它是InnoDB管理儲存空間的基本單位,一個頁的大小一般是16KBInnoDB為了不同的目的而設計了許多種不同類型的,例如存放表空間頭部資訊的頁,存放Insert Buffer資訊的頁,存放INODE資訊的頁,存放undo日誌資訊的頁等等等等。當然了,如果我說的這些名詞你一個都沒聽過,就當我放了個屁吧~ 不過這沒有一毛錢關係,我們今兒個也不准備說這些類型的頁,我們聚焦的是那些存放我們表中記錄的那種類型的頁,官方稱這種存放記錄的頁為索引(INDEX)頁,鑑於我們還沒有了解過索引是個什麼東西,而這些表中的記錄就是我們日常口中所稱的資料,所以目前還是叫這種存放記錄的頁為資料頁吧。

資料頁結構的快速瀏覽

資料頁所代表的這塊16KB大小的儲存空間可以分割成多個部分,不同部分有不同的功能,各個部分如圖所示:

從圖中可以看出,一個InnoDB資料頁的儲存空間大致被分割成了 7個部分,有的部分所佔用的位元組數是確定的,有的部分所佔用的位元組數是不確定的。下邊我們用表格的方式來大致描述一下這7個部分都儲存一些啥內容(快速的瞅一眼就行了,後邊會詳細嘮叨的):

頁頭位元組資料頁專有的一些資訊最小記錄和最大記錄位元組兩個虛擬的行記錄User Records不確定實際儲存的行記錄內容Free Space不確定頁中尚未使用的空間Page Directory#不確定#頁中的某些記錄的相對位置檔案尾號
名稱 中文名稱 佔用空間大小 #簡單描述
File Header 檔案頭 38位元組
Page Header
56 Infimum Supremum
26
使用者記錄
空閒空間
頁面目錄 File Trailer
######8###位元組#######校驗頁是否完整#############

記錄在頁中的儲存

在頁的7個組成部分中,我們自己儲存的記錄會依照我們指定的行格式儲存到User Records部分。但在一開始產生頁面的時候,其實並沒有User Records這個部分,每當我們插入一筆記錄,都會從Free Space部分,也就是尚未使用的儲存空間中申請一個記錄大小的空間分割到User Records部分,當Free Space部分的空間全部被User Records部分取代掉之後,也就代表這個頁使用完了,如果還有新的記錄插入的話,就需要去申請新的頁了,這個過程的圖示如下:

深入研究MySQL原理篇之InnoDB資料頁

為了更好的管理在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個列,其中c1c2列是用來儲存整數的,c3列是用來儲存字串的。要注意的是,我們把 c1 列指定為主鍵,所以在特定的行格式中InnoDB就沒必要為我們去創建那個所謂的 #row_id  隱藏列了。而且我們為這個表指定了ascii字元集以及Compact的行格式。所以這個表中記錄的行格式示意圖就是這樣的:

深入研究MySQL原理篇之InnoDB資料頁

從圖中可以看到,我們特意把記錄頭資訊的5個位元組的資料給標出來了,表示它很重要,我們再次先把這些記錄頭資訊中各個屬性的大體意思瀏覽一下(我們目前使用Compact行格式進行示範):

標記該記錄是否已刪除 ##14##3023表示最大記錄
名稱 大小(單位:bit) 描述
預留位元1 1 #沒有使用
預留位元2 1 沒有使用
##delete_mask 1
#min_rec_mask
B 樹的每層非葉子節點中的最小記錄都會加入該標記 n_owned
表示目前記錄擁有的記錄數 #heap_no # #13表示目前記錄在記錄堆的位置資訊record_type
表示目前記錄的類型,表示普通記錄,1表示B 樹非葉節點記錄,表示最小記錄,
#########next_record############16########表示下一記錄的相對位置############

由于我们现在主要在唠叨记录头信息的作用,所以为了大家理解上的方便,我们只在page_demo表的行格式演示图中画出有关的头信息属性以及c1c2c3列的信息(其他信息没画不代表它们不存在啊,只是为了理解上的方便在图中省略了~),简化后的行格式示意图就是这样:

深入研究MySQL原理篇之InnoDB資料頁

下边我们试着向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部分中是怎么表示的,我把记录中头信息和实际的列数据都用十进制表示出来了(其实是一堆二进制位),所以这些记录的示意图就是:

深入研究MySQL原理篇之InnoDB資料頁

看这个图的时候需要注意一下,各条记录在User Records中存储的时候并没有空隙,这里只是为了大家观看方便才把每条记录单独画在一行中。我们对照着这个图来看看记录头信息中的各个属性是啥意思:

  • delete_mask

    这个属性标记着当前记录是否被删除,占用1个二进制位,值为0的时候代表记录并没有被删除,为1的时候代表记录被删除掉了。

    啥?被删除的记录还在中么?是的,摆在台面上的和背地里做的可能大相径庭,你以为它删除了,可它还在真实的磁盘上[摊手](忽然想起冠希~)。这些被删除的记录之所以不立即从磁盘上移除,是因为移除它们之后把其他的记录在磁盘上重新排列需要性能消耗,所以只是打一个删除标记而已,所有被删除掉的记录都会组成一个所谓的垃圾链表,在这个链表中的记录占用的空间称之为所谓的可重用空间,之后如果有新记录插入到表中的话,可能把这些被删除的记录占用的存储空间覆盖掉。

  • min_rec_mask

    B+树的每层非叶子节点中的最小记录都会添加该标记,什么是个B+树?什么是个非叶子节点?好吧,等会再聊这个问题。反正我们自己插入的四条记录的min_rec_mask值都是0,意味着它们都不是B+树的非叶子节点中的最小记录。

  • n_owned

    这个暂时保密,稍后它是主角~

  • heap_no

    这个属性表示当前记录在本中的位置,从图中可以看出来,我们插入的4条记录在本中的位置分别是:2345。是不是少了点啥?是的,怎么不见heap_no值为01的记录呢?

    这其实是设计InnoDB的大叔们玩的一个小把戏,他们自动给每个页里边儿加了两个记录,由于这两个记录并不是我们自己插入的,所以有时候也称为伪记录或者虚拟记录。这两个伪记录一个代表最小记录,一个代表最大记录,等一下哈~,记录可以比大小么?

    是的,记录也可以比大小,对于一条完整的记录来说,比较记录的大小就是比较主键的大小。比方说我们插入的4行记录的主键值分别是:1234,这也就意味着这4条记录的大小从小到大依次递增。

  • 但是不管我们向中插入了多少自己的记录,设计InnoDB的大叔们都规定他们定义的两条伪记录分别为最小记录与最大记录。这两条记录的构造十分简单,都是由5字节大小的记录头信息和8字节大小的一个固定的部分组成的,如图所示

    深入研究MySQL原理篇之InnoDB資料頁

    由于这两条记录不是我们自己定义的记录,所以它们并不存放在User Records部分,他们被单独放在一个称为Infimum + Supremum的部分,如图所示:

    深入研究MySQL原理篇之InnoDB資料頁

    从图中我们可以看出来,最小记录和最大记录的heap_no值分别是01,也就是说它们的位置最靠前。

  • record_type

    这个属性表示当前记录的类型,一共有4种类型的记录,0表示普通记录,1表示B+树非叶节点记录,2表示最小记录,3表示最大记录。从图中我们也可以看出来,我们自己插入的记录就是普通记录,它们的record_type值都是0,而最小记录和最大记录的record_type值分别为23

    至于record_type1的情况,我们之后在说索引的时候会重点强调的。

  • next_record

    这玩意儿非常重要,它表示从当前记录的真实数据到下一条记录的真实数据的地址偏移量。比方说第一条记录的next_record值为32,意味着从第一条记录的真实数据的地址处向后找32个字节便是下一条记录的真实数据。如果你熟悉数据结构的话,就立即明白了,这其实是个链表,可以通过一条记录找到它的下一条记录。但是需要注意注意再注意的一点是,下一条记录指得并不是按照我们插入顺序的下一条记录,而是按照主键值由小到大的顺序的下一条记录。而且规定 Infimum记录(也就是最小记录) 的下一条记录就是本页中主键值最小的用户记录,而本页中主键值最大的用户记录的下一条记录就是 Supremum记录(也就是最大记录) ,为了更形象的表示一下这个next_record起到的作用,我们用箭头来替代一下next_record中的地址偏移量:

    深入研究MySQL原理篇之InnoDB資料頁

    从图中可以看出来,我们的记录按照主键从小到大的顺序形成了一个单链表。最大记录next_record的值为0,这也就是说最大记录是没有下一条记录了,它是这个单链表中的最后一个节点。如果从中删除掉一条记录,这个链表也是会跟着变化的,比如我们把第2条记录删掉:

    mysql> DELETE FROM page_demo WHERE c1 = 2;
    Query OK, 1 row affected (0.02 sec)

    删掉第2条记录后的示意图就是:

    深入研究MySQL原理篇之InnoDB資料頁

    从图中可以看出来,删除第2条记录前后主要发生了这些变化:

    • 第2条记录并没有从存储空间中移除,而是把该条记录的delete_mask值设置为1
    • 第2条记录的next_record值变为了0,意味着该记录没有下一条记录了。
    • 第1条记录的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)

我们看一下记录的存储情况:

深入研究MySQL原理篇之InnoDB資料頁

从图中可以看到,InnoDB并没有因为新记录的插入而为它申请新的存储空间,而是直接复用了原来被删除记录的存储空间。

Page Directory(页目录)

现在我们了解了记录在页中按照主键值由小到大顺序串联成一个单链表,那如果我们想根据主键值查找页中的某条记录该咋办呢?比如说这样的查询语句:

SELECT * FROM page_demo WHERE c1 = 3;

最笨的办法:从Infimum记录(最小记录)开始,沿着链表一直往后找,总有一天会找到(或者找不到[摊手]),在找的时候还能投机取巧,因为链表中各个记录的值是按照从小到大顺序排列的,所以当链表的某个节点代表的记录的主键值大于你想要查找的主键值时,你就可以停止查找了,因为该节点后边的节点的主键值依次递增。

這個方法在頁中儲存的記錄數量比較少的情況用起來也沒啥問題,比方說現在我們的表裡只有4條自己插入的記錄,所以最多找 4次就可以把所有記錄都遍歷一遍,但是如果一個頁中儲存了非常多的記錄,那麼查找對效能來說還是有損耗的,所以我們說這種遍歷查找這是一個辦法。但是設計InnoDB的大叔們是什麼人,他們能用這麼笨的辦法麼,當然是要設計一種更6的查找方式嘍,他們從書的目錄中找到了靈感。

我們平常想從一本書中查找某個內容的時候,一般會先看目錄,找到需要查找的內容對應的書的頁碼,然後到對應的頁碼查看內容。設計InnoDB的大叔們也為我們的記錄製作了一個類似的目錄,他們的製作過程是這樣的:

  1. 將所有正常的記錄(包括最大和最小記錄,不包括標記為已刪除的記錄)劃分為幾個群組。

  2. 每個群組的最後一筆記錄(也就是群組內最大的那筆記錄)的頭資訊中的n_owned屬性表示該記錄擁有多少筆記錄,也就是該組內共有幾筆記錄。

  3. 將每個群組的最後一筆記錄的位址偏移量單獨提取出來按順序儲存到靠近的尾部的地方,這個地方就是所謂的Page Directory,也就是頁目錄(此時應該回傳頭看看頁面各部分的圖)。頁面目錄中的這些位址偏移稱為插槽(英文名稱:Slot),所以這個頁面目錄就是由插槽組成的。

比方說現在的page_demo表中正常的記錄共有6條,InnoDB會把它們分成兩組,第一組中只有一個最小記錄,第二組中是剩餘的5筆記錄,看下邊的示意圖:

深入研究MySQL原理篇之InnoDB資料頁

#從這個圖中我們需要注意這麼幾點:

  • 現在頁目錄部分中有兩個槽,也就表示我們的記錄被分成了兩個群組,槽1中的值是112,代表最大記錄的位址偏移(就是從頁面的0位元組開始數,數112個位元組);槽0中的值是 99,代表最小記錄的位址偏移。

  • 注意最小和最大記錄的頭資訊中的n_owned#屬性

    • 最小記錄的n_owned值為1,這就代表著以最小記錄結尾的這個分組中只有1筆記錄,也就是最小記錄本身。
    • 最大記錄的n_owned值為5,這就代表著以最大記錄結尾的這個分組中只有5筆記錄,包括最大記錄本身還有我們自己插入的4筆記錄。

99112這樣的位址偏移量很不直觀,我們用箭頭指向的方式取代數字,這樣更容易我們理解,所以修改後的示意圖就是這樣:

深入研究MySQL原理篇之InnoDB資料頁

哎呀,咋看上去怪怪的,這麼亂的圖對於我這個強迫症真是不能忍,那我們就暫時不管各筆記錄在儲存裝置上的排列方式了,單純從邏輯上看一下這些記錄和頁目錄的關係:

深入研究MySQL原理篇之InnoDB資料頁

##這樣看就順眼多了嘛!為什麼最小記錄的

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个组,如图所示:

深入研究MySQL原理篇之InnoDB資料頁

因为把16条记录的全部信息都画在一张图里太占地方,让人眼花缭乱的,所以只保留了用户记录头信息中的n_ownednext_record属性,也省略了各个记录之间的箭头,我没画不等于没有啊!现在看怎么从这个页目录中查找记录。因为各个槽代表的记录的主键值都是从小到大排序的,所以我们可以使用所谓的二分法来进行快速查找。5个槽的编号分别是:01234,所以初始情况下最低的槽就是low=0,最高的槽就是high=4。比方说我们想找主键值为6的记录,过程是这样的:

  1. 计算中间槽的位置:(0+4)/2=2,所以查看槽2对应记录的主键值为8,又因为8 > 6,所以设置high=2low保持不变。

  2. 重新计算中间槽的位置:(0+2)/2=1,所以查看槽1对应的主键值为4,又因为4 ,所以设置<code>low=1high保持不变。

  3. 因为high - low的值为1,所以确定主键值为6的记录在槽2对应的组中。此刻我们需要找到槽2中主键值最小的那条记录,然后沿着单向链表遍历槽2中的记录。但是我们前边又说过,每个槽对应的记录都是该组中主键值最大的记录,这里槽2对应的记录是主键值为8的记录,怎么定位一个组中最小的记录呢?别忘了各个槽都是挨着的,我们可以很轻易的拿到槽1对应的记录(主键值为4),该条记录的下一条记录就是槽2中主键值最小的记录,该记录的主键值为5。所以我们可以从这条主键值为5的记录出发,遍历槽2中的各条记录,直到找到主键值为6的那条记录即可。由于一个组中包含的记录条数只能是1~8条,所以遍历一个组中的记录的代价是很小的。

所以在一个数据页中查找指定主键值的记录的过程分为两步:

  1. 通过二分法确定该记录所在的槽,并找到该槽所在分组中主键值最小的那条记录。

  2. 通过记录的next_record属性遍历该槽所在的组中的各个记录。

Page Header(页面头部)

设计InnoDB的大叔们为了能得到一个数据页中存储的记录的状态信息,比如本页中已经存储了多少条记录,第一条记录的地址是什么,页目录中存储了多少个槽等等,特意在页中定义了一个叫Page Header的部分,它是结构的第二部分,这个部分占用固定的56个字节,专门存储各种状态信息,具体各个字节都是干嘛的看下表:

2位元組2位元組位元組記錄插入的方向位元組一個方向連續插入的記錄數量位元組該頁中記錄的數量(不包括最小和最大記錄以及被標記為刪除的記錄)位元組修改目前頁的最大交易ID,該值僅在二級索引中定義位元組B 樹中目前頁的層級
名稱 佔用空間大小 描述
# 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
#最後插入記錄的位置 ##PAGE_DIRECTION 2
#PAGE_N_DIRECTION 2
PAGE_N_RECS 2
#PAGE_MAX_TRX_ID 8
PAGE_LEVEL 2
######################################## #####PAGE_INDEX_ID############8###位元組######索引ID,表示目前頁屬於哪個索引########### ####PAGE_BTR_SEG_LEAF############10####位元組######B 樹葉子段的頭部訊息,僅在B 樹的Root頁定義#### ###########PAGE_BTR_SEG_TOP#############10###位元組######B 樹非葉子區段的頭部訊息,僅在B 樹的Root頁定義############

如果大家認真看過前邊的文章,從PAGE_N_DIR_SLOTSPAGE_LAST_INSERT以及PAGE_N_RECS的意思大家一定是清楚的,如果不清楚,對不起,你應該回頭再看一次前邊的文章。剩下的狀態資訊看不明白不要著急,飯要一口一口吃,東西要一點一點學(一定要稍安勿躁哦,不要被這些名詞嚇到)。這裡我們先嘮叨一下PAGE_DIRECTIONPAGE_N_DIRECTION的意思:

  • ##PAGE_DIRECTION

    假如新插入的一筆記錄的主鍵值比上一筆記錄的主鍵值大,我們說這條記錄的插入方向是右邊,反之則是左邊。用來表示最後一筆記錄插入方向的狀態就是

    PAGE_DIRECTION

  • PAGE_N_DIRECTION

    假設連續幾次插入新記錄的方向都是一致的,

    InnoDB會把沿著同一個方向插入記錄的條數記下來,這個條數就用PAGE_N_DIRECTION這個狀態表示。當然,如果最後一筆記錄的插入方向改變了的話,這個狀態的值會被清除重新統計。

至於我們沒提到的那些屬性,我沒說是因為現在不需要大家知道。不要急,當我們學完了後邊的內容,你再回頭看,一切都是如此清晰。

File Header(檔案頭)

上邊嘮叨的

Page Header是專門針對資料頁記錄的各種狀態信息,比方說頁裡頭有多少個記錄了呀,有多少個槽了呀。我們現在描述的File Header針對各種類型的頁都通用,也就是說不同類型的頁都會以File Header作為第一個組成部分,它描述了一些針對各種頁都通用的一些信息,比方說這個頁的編號是多少,它的上一個頁、下一個頁是誰啦吧啦吧啦~ 這個部分佔用固定的38個字節,是由下邊這些內容組成的:

名稱#佔用空間大小描述頁的校驗和(checksum值)頁號上一個頁的頁號下一個頁的頁號##FIL_PAGE_LSN8FIL_PAGE_TYPE2##FIL_PAGE_FILE_FLUSH_LSN位元組FIL_PAGE_ARCH_LOG_NO_OR_SPACE_ID位元組 #

對照著這個表格,我們看幾個目前比較重要的部分:

  • #FIL_PAGE_SPACE_OR_CHKSUM

    這個代表目前頁面的校驗和(checksum)。啥是校驗和?就是對於一個很長很長的位元組字串來說,我們會透過某種演算法來計算一個比較短的值來代表這個很長的位元組串,這個比較短的值就稱為校驗和。這樣在比較兩個很長的位元組字串之前先比較這兩個長位元組串的校驗和,如果校驗和都不一樣兩個長字節字串肯定是不同的,所以省去了直接比較兩個比較長的位元組字串的時間損耗。

  • FIL_PAGE_OFFSET

    每一個都有一個單獨的頁號,就跟你的身分證號碼一樣,InnoDB透過頁號來可以唯一定位一個

  • FIL_PAGE_TYPE

    這個代表目前的類型,我們前邊說過,InnoDB#為了不同的目的而把頁分為不同的類型,我們上邊介紹的其實都是儲存記錄的資料頁,其實還有很多別的類型的頁,具體如下表:

#FIL_PAGE_SPACE_OR_CHKSUM 4位元組
FIL_PAGE_OFFSET 4位元組
#FIL_PAGE_PREV 4位元組
FIL_PAGE_NEXT 4位元組
位元組頁面被最後修改時對應的日誌序列位置(英文名稱是:Log Sequence Number)
位元組該頁的型別
8僅在系統表空間的一個頁中定義,代表檔案至少被刷新到了對應的LSN值
#4頁屬於哪個表空間
0x0002 0x0003#0x0004FIL_PAGE_TYPE_SYSFIL_PAGE_TYPE_TRX_SYS0x0008#FIL_PAGE_TYPE_XDES
類型名稱 十六進位 描述
#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
######0x0009## ####擴充描述頁###############FIL_PAGE_TYPE_BLOB#########0x000A######溢頁######################################## ######FIL_PAGE_INDEX#########0x45BF#######索引頁,也就是我們所說的###資料頁############## ##

我們存放記錄的資料頁的型別其實是FIL_PAGE_INDEX,也就是所謂的索引頁。至於啥是索引,而聽下回分解~

  • FIL_PAGE_PREVFIL_PAGE_NEXT

    #我們前邊強調過,InnoDB都是以頁為單位存放資料的,有時候我們存放某種類型的資料佔用的空間非常大(比方說一張表中可以有成千上萬筆記錄),InnoDB可能不可以一次為這麼多資料分配一個非常大的儲存空間,如果分散到多個不連續的頁中儲存的話需要把這些頁關聯起來,FIL_PAGE_PREVFIL_PAGE_NEXT 就分別代表本頁的上一個和下一個頁的頁號。這樣透過建立一個雙向鍊錶把許許多多的頁就都串聯起來了,而無需這些頁在物理上真正連著。要注意的是,並不是所有類型的頁都有上一個和下一個頁的屬性,不過我們本集中嘮叨的資料頁(也就是類型為FIL_PAGE_INDEX的頁)是有這兩個屬性的,所以所有的資料頁其實是雙鍊錶,就像這樣:

    深入研究MySQL原理篇之InnoDB資料頁

  • 關於 File Header的其他屬性我們暫時用不到,等用到的時候再提哈~

    File Trailer

    我們知道InnoDB儲存引擎會把資料儲存到磁碟上,但是磁碟速度太慢,需要以為單位把資料載入到記憶體中處理,如果該頁中的資料在記憶體中被修改了,那麼在修改後的某個時間需要把資料同步到磁碟。但是在同步了一半的時候中斷電了咋辦,這不是莫名尷尬麼?為了偵測一個頁面是否完整(也就是在同步的時候有沒有發生只同步一半的尷尬情況),設計InnoDB的大叔們在每個頁的尾部都加了一個File Trailer 部分,這個部分由8個位元組組成,可以分成2個小部分:

    • 前4個位元組代表頁的校驗和

      這個部分是和File Header中的校驗和相對應的。每當一個頁面在記憶體中修改了,在同步之前就要把它的校驗和算出來,因為File Header在頁面的前邊,所以校驗和會被先同步到磁碟,當完全寫完時,校驗和也會被寫到頁的尾部,如果完全同步成功,則頁的首部和尾部的校驗和應該是一致的。如果寫了一半兒斷電了,那麼在File Header中的校驗和就代表著已經修改過的頁,而在File Trailer中的校驗和代表著原先的頁,二者不同則表示同步中間出錯。

    • 後4個位元組代表頁面被最後修改時對應的日誌序列位置(LSN)

      這個部分也是為了校驗頁的完整性的,只不過我們目前還沒說LSN是個什麼意思,所以大家可以先不用管這個屬性。

    這個File TrailerFile Header類似,都是所有類型的頁通用的。

    總結

    1. InnoDB為了不同的目的而設計了不同類型的頁,我們把用來存放記錄的頁叫做資料頁

    2. 一個資料頁可以大致分割為7個部分,分別是

      • File Header,表示頁的一些通用訊息,佔固定的38位元組。
      • Page Header,表示資料頁專有的一些訊息,佔固定的56個位元組。
      • Infimum Supremum,兩個虛擬的偽記錄,分別表示頁中的最小和最大記錄,佔固定的26個位元組。
      • User Records:真實儲存我們插入的記錄的部分,大小不固定。
      • Free Space:頁中尚未使用的部分,大小不確定。
      • Page Directory:頁中的某些記錄相對位置,也就是各個槽在頁面中的位址偏移量,大小不固定,插入的記錄越多,這個部分佔用的空間越多。
      • File Trailer:用來檢驗頁是否完整的部分,佔用固定的8個位元組。
    3. 每個記錄的頭資訊中都有一個next_record屬性,從而使頁中的所有記錄串連成一個單鍊錶

    4. InnoDB會把頁中的記錄分成若干個群組,每個群組的最後一個記錄的位址偏移量當作一個,存放在Page Directory中,所以在一個頁中根據主鍵查找記錄是非常快的,分為兩個步驟:

      • ##透過二分法確定該記錄所在的插槽。

      • 透過記錄的next_record屬性遍歷該插槽所在的群組中的各個記錄。

    5. 每個資料頁的File Header部分都有上一個和下一個頁的編號,所以所有的資料頁會組成一個雙鍊錶

    6. 為確保從記憶體中同步到磁碟的頁的完整性,在頁的首部和尾部都會儲存頁中資料的校驗和和頁面最後修改時對應的LSN值,如果首部和尾部的校驗和和LSN值校驗不成功的話,就表示同步過程出現了問題。

    推薦學習:mysql影片教學

    以上是深入研究MySQL原理篇之InnoDB資料頁的詳細內容。更多資訊請關注PHP中文網其他相關文章!

    陳述:
    本文轉載於:csdn.net。如有侵權,請聯絡admin@php.cn刪除