MySQL中InnoDB數據頁的原理是什么

MySQL中InnoDB數據頁的原理是什么,相信很多沒有經驗的人對此束手無策,為此本文總結了問題出現的原因和解決方法,通過這篇文章希望你能解決這個問題。

創(chuàng)新互聯(lián)主營商南網站建設的網絡公司,主營網站建設方案,app軟件定制開發(fā),商南h5成都微信小程序搭建,商南網站營銷推廣歡迎商南等地區(qū)企業(yè)咨詢

不同類型的頁簡介

它是InnoDB管理存儲空間的基本單位,一個頁的大小一般是16KB。InnoDB為了不同的目的而設計了許多種不同類型的,比如存放表空間頭部信息的頁,存放Insert Buffer信息的頁,存放INODE信息的頁,存放undo日志信息的頁等等等等。當然了,如果我說的這些名詞你一個都沒有聽過,就當我放了個屁吧~ 不過這沒有一毛錢關系,我們今兒個也不準備說這些類型的頁,我們聚焦的是那些存放我們表中記錄的那種類型的頁,官方稱這種存放記錄的頁為索引(INDEX)頁,鑒于我們還沒有了解過索引是個什么東西,而這些表中的記錄就是我們日??谥兴Q的數據,所以目前還是叫這種存放記錄的頁為數據頁吧。

數據頁結構的快速瀏覽

數據頁代表的這塊16KB大小的存儲空間可以被劃分為多個部分,不同部分有不同的功能,各個部分如圖所示:

MySQL中InnoDB數據頁的原理是什么

從圖中可以看出,一個InnoDB數據頁的存儲空間大致被劃分成了7個部分,有的部分占用的字節(jié)數是確定的,有的部分占用的字節(jié)數是不確定的。下邊我們用表格的方式來大致描述一下這7個部分都存儲一些啥內容(快速的瞅一眼就行了,后邊會詳細嘮叨的):

名稱中文名占用空間大小簡單描述
File Header文件頭部38字節(jié)頁的一些通用信息
Page Header頁面頭部56字節(jié)數據頁專有的一些信息
Infimum + Supremum最小記錄和最大記錄26字節(jié)兩個虛擬的行記錄
User Records用戶記錄不確定實際存儲的行記錄內容
Free Space空閑空間不確定頁中尚未使用的空間
Page Directory頁面目錄不確定頁中的某些記錄的相對位置
File Trailer文件尾部8字節(jié)校驗頁是否完整

記錄在頁中的存儲

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

MySQL中InnoDB數據頁的原理是什么

為了更好的管理在User Records中的這些記錄,InnoDB可費了一番力氣呢,在哪費力氣了呢?不就是把記錄按照指定的行格式一條一條擺在User Records部分么?其實這話還得從記錄行格式的記錄頭信息中說起。

記錄頭信息的秘密

為了故事的順利發(fā)展,我們先創(chuàng)建一個表:

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)

這個新創(chuàng)建的page_demo表有3個列,其中c1c2列是用來存儲整數的,c3列是用來存儲字符串的。需要注意的是,我們把 c1 列指定為主鍵,所以在具體的行格式中InnoDB就沒必要為我們去創(chuàng)建那個所謂的 row_id 隱藏列了。而且我們?yōu)檫@個表指定了ascii字符集以及Compact的行格式。所以這個表中記錄的行格式示意圖就是這樣的:

MySQL中InnoDB數據頁的原理是什么

從圖中可以看到,我們特意把記錄頭信息的5個字節(jié)的數據給標出來了,說明它很重要,我們再次先把這些記錄頭信息中各個屬性的大體意思瀏覽一下(我們目前使用Compact行格式進行演示):

名稱大?。▎挝唬篵it)描述
預留位11沒有使用
預留位21沒有使用
delete_mask1標記該記錄是否被刪除
min_rec_mask1B+樹的每層非葉子節(jié)點中的最小記錄都會添加該標記
n_owned4表示當前記錄擁有的記錄數
heap_no13表示當前記錄在記錄堆的位置信息
record_type3表示當前記錄的類型,0表示普通記錄,1表示B+樹非葉節(jié)點記錄,2表示最小記錄,3表示最大記錄
next_record16表示下一條記錄的相對位置

由于我們現在主要在嘮叨記錄頭信息的作用,所以為了大家理解上的方便,我們只在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+樹的每層非葉子節(jié)點中的最小記錄都會添加該標記,什么是個B+樹?什么是個非葉子節(jié)點?好吧,等會再聊這個問題。反正我們自己插入的四條記錄的min_rec_mask值都是0,意味著它們都不是B+樹的非葉子節(jié)點中的最小記錄。

  • n_owned

    這個暫時保密,稍后它是主角~

  • heap_no

    這個屬性表示當前記錄在本中的位置,從圖中可以看出來,我們插入的4條記錄在本中的位置分別是:23、45。是不是少了點啥?是的,怎么不見heap_no值為01的記錄呢?

    這其實是設計InnoDB的大叔們玩的一個小把戲,他們自動給每個頁里邊兒加了兩個記錄,由于這兩個記錄并不是我們自己插入的,所以有時候也稱為偽記錄或者虛擬記錄。這兩個偽記錄一個代表最小記錄,一個代表最大記錄,等一下哈~,記錄可以比大小么?

    是的,記錄也可以比大小,對于一條完整的記錄來說,比較記錄的大小就是比較主鍵的大小。比方說我們插入的4行記錄的主鍵值分別是:1、2、34,這也就意味著這4條記錄的大小從小到大依次遞增。

  • 但是不管我們向中插入了多少自己的記錄,設計InnoDB的大叔們都規(guī)定他們定義的兩條偽記錄分別為最小記錄與最大記錄。這兩條記錄的構造十分簡單,都是由5字節(jié)大小的記錄頭信息和8字節(jié)大小的一個固定的部分組成的,如圖所示

    MySQL中InnoDB數據頁的原理是什么

    由于這兩條記錄不是我們自己定義的記錄,所以它們并不存放在User Records部分,他們被單獨放在一個稱為Infimum + Supremum的部分,如圖所示:

    MySQL中InnoDB數據頁的原理是什么

    從圖中我們可以看出來,最小記錄和最大記錄的heap_no值分別是01,也就是說它們的位置最靠前。

  • record_type

    這個屬性表示當前記錄的類型,一共有4種類型的記錄,0表示普通記錄,1表示B+樹非葉節(jié)點記錄,2表示最小記錄,3表示最大記錄。從圖中我們也可以看出來,我們自己插入的記錄就是普通記錄,它們的record_type值都是0,而最小記錄和最大記錄的record_type值分別為23。

    至于record_type1的情況,我們之后在說索引的時候會重點強調的。

  • next_record

    這玩意兒非常重要,它表示從當前記錄的真實數據到下一條記錄的真實數據的地址偏移量。比方說第一條記錄的next_record值為32,意味著從第一條記錄的真實數據的地址處向后找32個字節(jié)便是下一條記錄的真實數據。如果你熟悉數據結構的話,就立即明白了,這其實是個鏈表,可以通過一條記錄找到它的下一條記錄。但是需要注意注意再注意的一點是,下一條記錄指得并不是按照我們插入順序的下一條記錄,而是按照主鍵值由小到大的順序的下一條記錄。而且規(guī)定 Infimum記錄(也就是最小記錄) 的下一條記錄就是本頁中主鍵值最小的用戶記錄,而本頁中主鍵值最大的用戶記錄的下一條記錄就是 Supremum記錄(也就是最大記錄) ,為了更形象的表示一下這個next_record起到的作用,我們用箭頭來替代一下next_record中的地址偏移量:

    MySQL中InnoDB數據頁的原理是什么

    從圖中可以看出來,我們的記錄按照主鍵從小到大的順序形成了一個單鏈表。最大記錄next_record的值為0,這也就是說最大記錄是沒有下一條記錄了,它是這個單鏈表中的最后一個節(jié)點。如果從中刪除掉一條記錄,這個鏈表也是會跟著變化的,比如我們把第2條記錄刪掉:

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

    刪掉第2條記錄后的示意圖就是:

    MySQL中InnoDB數據頁的原理是什么

    從圖中可以看出來,刪除第2條記錄前后主要發(fā)生了這些變化:

    所以,不論我們怎么對頁中的記錄做增刪改操作,InnoDB始終會維護一條記錄的單鏈表,鏈表中的各個節(jié)點是按照主鍵值由小到大的順序連接起來的。

    • 第2條記錄并沒有從存儲空間中移除,而是把該條記錄的delete_mask值設置為1

    • 第2條記錄的next_record值變?yōu)榱?,意味著該記錄沒有下一條記錄了。

    • 第1條記錄的next_record指向了第3條記錄。

    • 還有一點你可能忽略了,就是最大記錄n_owned值從5變成了4,關于這一點的變化我們稍后會詳細說明的。

再來看一個有意思的事情,因為主鍵值為2的記錄被我們刪掉了,但是存儲空間卻沒有回收,如果我們再次把這條記錄插入到表中,會發(fā)生什么事呢?

mysql> INSERT INTO page_demo VALUES(2, 200, 'bbbb');
Query OK, 1 row affected (0.00 sec)

我們看一下記錄的存儲情況:

MySQL中InnoDB數據頁的原理是什么

從圖中可以看到,InnoDB并沒有因為新記錄的插入而為它申請新的存儲空間,而是直接復用了原來被刪除記錄的存儲空間。

Page Directory(頁目錄)

現在我們了解了記錄在頁中按照主鍵值由小到大順序串聯(lián)成一個單鏈表,那如果我們想根據主鍵值查找頁中的某條記錄該咋辦呢?比如說這樣的查詢語句:

SELECT * FROM page_demo WHERE c1 = 3;

最笨的辦法:從Infimum記錄(最小記錄)開始,沿著鏈表一直往后找,總有一天會找到(或者找不到[攤手]),在找的時候還能投機取巧,因為鏈表中各個記錄的值是按照從小到大順序排列的,所以當鏈表的某個節(jié)點代表的記錄的主鍵值大于你想要查找的主鍵值時,你就可以停止查找了,因為該節(jié)點后邊的節(jié)點的主鍵值依次遞增。

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

我們平常想從一本書中查找某個內容的時候,一般會先看目錄,找到需要查找的內容對應的書的頁碼,然后到對應的頁碼查看內容。設計InnoDB的大叔們?yōu)槲覀兊挠涗浺仓谱髁艘粋€類似的目錄,他們的制作過程是這樣的:

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

  2. 每個組的最后一條記錄(也就是組內最大的那條記錄)的頭信息中的n_owned屬性表示該記錄擁有多少條記錄,也就是該組內共有幾條記錄。

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

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

MySQL中InnoDB數據頁的原理是什么

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

  • 現在頁目錄部分中有兩個槽,也就意味著我們的記錄被分成了兩個組,槽1中的值是112,代表最大記錄的地址偏移量(就是從頁面的0字節(jié)開始數,數112個字節(jié));槽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的大叔們對每個分組中的記錄條數是有規(guī)定的:對于最小記錄所在的分組只能有 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個槽的編號分別是:0、1、2、34,所以初始情況下最低的槽就是low=0,最高的槽就是high=4。比方說我們想找主鍵值為6的記錄,過程是這樣的:

  1. 計算中間槽的位置:(0+4)/2=2,所以查看槽2對應記錄的主鍵值為8,又因為8 > 6,所以設置high=2,low保持不變。

  2. 重新計算中間槽的位置:(0+2)/2=1,所以查看槽1對應的主鍵值為4,又因為4 < 6,所以設置low=1,high保持不變。

  3. 因為high - low的值為1,所以確定主鍵值為6的記錄在槽2對應的組中。此刻我們需要找到槽2中主鍵值最小的那條記錄,然后沿著單向鏈表遍歷槽2中的記錄。但是我們前邊又說過,每個槽對應的記錄都是該組中主鍵值最大的記錄,這里槽2對應的記錄是主鍵值為8的記錄,怎么定位一個組中最小的記錄呢?別忘了各個槽都是挨著的,我們可以很輕易的拿到槽1對應的記錄(主鍵值為4),該條記錄的下一條記錄就是槽2中主鍵值最小的記錄,該記錄的主鍵值為5。所以我們可以從這條主鍵值為5的記錄出發(fā),遍歷槽2中的各條記錄,直到找到主鍵值為6的那條記錄即可。由于一個組中包含的記錄條數只能是1~8條,所以遍歷一個組中的記錄的代價是很小的。

所以在一個數據頁中查找指定主鍵值的記錄的過程分為兩步:

  1. 通過二分法確定該記錄所在的槽,并找到該槽所在分組中主鍵值最小的那條記錄。

  2. 通過記錄的next_record屬性遍歷該槽所在的組中的各個記錄。

Page Header(頁面頭部)

設計InnoDB的大叔們?yōu)榱四艿玫揭粋€數據頁中存儲的記錄的狀態(tài)信息,比如本頁中已經存儲了多少條記錄,第一條記錄的地址是什么,頁目錄中存儲了多少個槽等等,特意在頁中定義了一個叫Page Header的部分,它是結構的第二部分,這個部分占用固定的56個字節(jié),專門存儲各種狀態(tài)信息,具體各個字節(jié)都是干嘛的看下表:

名稱占用空間大小描述
PAGE_N_DIR_SLOTS2字節(jié)在頁目錄中的槽數量
PAGE_HEAP_TOP2字節(jié)還未使用的空間最小地址,也就是說從該地址之后就是Free Space
PAGE_N_HEAP2字節(jié)本頁中的記錄的數量(包括最小和最大記錄以及標記為刪除的記錄)
PAGE_FREE2字節(jié)第一個已經標記為刪除的記錄地址(各個已刪除的記錄通過next_record也會組成一個單鏈表,這個單鏈表中的記錄可以被重新利用)
PAGE_GARBAGE2字節(jié)已刪除記錄占用的字節(jié)數
PAGE_LAST_INSERT2字節(jié)最后插入記錄的位置
PAGE_DIRECTION2字節(jié)記錄插入的方向
PAGE_N_DIRECTION2字節(jié)一個方向連續(xù)插入的記錄數量
PAGE_N_RECS2字節(jié)該頁中記錄的數量(不包括最小和最大記錄以及被標記為刪除的記錄)
PAGE_MAX_TRX_ID8字節(jié)修改當前頁的最大事務ID,該值僅在二級索引中定義
PAGE_LEVEL2字節(jié)當前頁在B+樹中所處的層級
PAGE_INDEX_ID8字節(jié)索引ID,表示當前頁屬于哪個索引
PAGE_BTR_SEG_LEAF10字節(jié)B+樹葉子段的頭部信息,僅在B+樹的Root頁定義
PAGE_BTR_SEG_TOP10字節(jié)B+樹非葉子段的頭部信息,僅在B+樹的Root頁定義

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

  • PAGE_DIRECTION

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

  • PAGE_N_DIRECTION

    假設連續(xù)幾次插入新記錄的方向都是一致的,InnoDB會把沿著同一個方向插入記錄的條數記下來,這個條數就用PAGE_N_DIRECTION這個狀態(tài)表示。當然,如果最后一條記錄的插入方向改變了的話,這個狀態(tài)的值會被清零重新統(tǒng)計。

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

File Header(文件頭部)

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

名稱占用空間大小描述
FIL_PAGE_SPACE_OR_CHKSUM4字節(jié)頁的校驗和(checksum值)
FIL_PAGE_OFFSET4字節(jié)頁號
FIL_PAGE_PREV4字節(jié)上一個頁的頁號
FIL_PAGE_NEXT4字節(jié)下一個頁的頁號
FIL_PAGE_LSN8字節(jié)頁面被最后修改時對應的日志序列位置(英文名是:Log Sequence Number)
FIL_PAGE_TYPE2字節(jié)該頁的類型
FIL_PAGE_FILE_FLUSH_LSN8字節(jié)僅在系統(tǒng)表空間的一個頁中定義,代表文件至少被刷新到了對應的LSN值
FIL_PAGE_ARCH_LOG_NO_OR_SPACE_ID4字節(jié)頁屬于哪個表空間

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

  • FIL_PAGE_SPACE_OR_CHKSUM

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

  • FIL_PAGE_OFFSET

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

  • FIL_PAGE_TYPE

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

    類型名稱十六進制描述
    FIL_PAGE_TYPE_ALLOCATED0x0000最新分配,還沒使用
    FIL_PAGE_UNDO_LOG0x0002Undo日志頁
    FIL_PAGE_INODE0x0003段信息節(jié)點
    FIL_PAGE_IBUF_FREE_LIST0x0004Insert Buffer空閑列表
    FIL_PAGE_IBUF_BITMAP0x0005Insert Buffer位圖
    FIL_PAGE_TYPE_SYS0x0006系統(tǒng)頁
    FIL_PAGE_TYPE_TRX_SYS0x0007事務系統(tǒng)數據
    FIL_PAGE_TYPE_FSP_HDR0x0008表空間頭部信息
    FIL_PAGE_TYPE_XDES0x0009擴展描述頁
    FIL_PAGE_TYPE_BLOB0x000A溢出頁
    FIL_PAGE_INDEX0x45BF索引頁,也就是我們所說的數據頁

    我們存放記錄的數據頁的類型其實是FIL_PAGE_INDEX,也就是所謂的索引頁。至于啥是個索引,且聽下回分解~

  • FIL_PAGE_PREVFIL_PAGE_NEXT

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

    MySQL中InnoDB數據頁的原理是什么

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

File Trailer

我們知道InnoDB存儲引擎會把數據存儲到磁盤上,但是磁盤速度太慢,需要以為單位把數據加載到內存中處理,如果該頁中的數據在內存中被修改了,那么在修改后的某個時間需要把數據同步到磁盤中。但是在同步了一半的時候中斷電了咋辦,這不是莫名尷尬么?為了檢測一個頁是否完整(也就是在同步的時候有沒有發(fā)生只同步一半的尷尬情況),設計InnoDB的大叔們在每個頁的尾部都加了一個File Trailer部分,這個部分由8個字節(jié)組成,可以分成2個小部分:

  • 前4個字節(jié)代表頁的校驗和

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

  • 后4個字節(jié)代表頁面被最后修改時對應的日志序列位置(LSN)

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

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

總結

  1. InnoDB為了不同的目的而設計了不同類型的頁,我們把用于存放記錄的頁叫做數據頁。

  2. 一個數據頁可以被大致劃分為7個部分,分別是

    • File Header,表示頁的一些通用信息,占固定的38字節(jié)。

    • Page Header,表示數據頁專有的一些信息,占固定的56個字節(jié)。

    • Infimum + Supremum,兩個虛擬的偽記錄,分別表示頁中的最小和最大記錄,占固定的26個字節(jié)。

    • User Records:真實存儲我們插入的記錄的部分,大小不固定。

    • Free Space:頁中尚未使用的部分,大小不確定。

    • Page Directory:頁中的某些記錄相對位置,也就是各個槽在頁面中的地址偏移量,大小不固定,插入的記錄越多,這個部分占用的空間越多。

    • File Trailer:用于檢驗頁是否完整的部分,占用固定的8個字節(jié)。

  3. 每個記錄的頭信息中都有一個next_record屬性,從而使頁中的所有記錄串聯(lián)成一個單鏈表。

  4. InnoDB會把頁中的記錄劃分為若干個組,每個組的最后一個記錄的地址偏移量作為一個,存放在Page Directory中,所以在一個頁中根據主鍵查找記錄是非??斓?,分為兩步:

    • 通過二分法確定該記錄所在的槽。

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

  5. 每個數據頁的File Header部分都有上一個和下一個頁的編號,所以所有的數據頁會組成一個雙鏈表。

  6. 為保證從內存中同步到磁盤的頁的完整性,在頁的首部和尾部都會存儲頁中數據的校驗和和頁面最后修改時對應的LSN值,如果首部和尾部的校驗和和LSN值校驗不成功的話,就說明同步過程出現了問題。

看完上述內容,你們掌握MySQL中InnoDB數據頁的原理是什么的方法了嗎?如果還想學到更多技能或想了解更多相關內容,歡迎關注創(chuàng)新互聯(lián)行業(yè)資訊頻道,感謝各位的閱讀!

新聞標題:MySQL中InnoDB數據頁的原理是什么
文章地址:http://bm7419.com/article42/jdgehc.html

成都網站建設公司_創(chuàng)新互聯(lián),為您提供企業(yè)建站、建站公司、企業(yè)網站制作、關鍵詞優(yōu)化、微信公眾號、ChatGPT

廣告

聲明:本網站發(fā)布的內容(圖片、視頻和文字)以用戶投稿、用戶轉載內容為主,如果涉及侵權請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內容未經允許不得轉載,或轉載時需注明來源: 創(chuàng)新互聯(lián)

成都網頁設計公司