中文字幕日韩精品一区二区免费_精品一区二区三区国产精品无卡在_国精品无码专区一区二区三区_国产αv三级中文在线

MySQL中InnoDB數(shù)據(jù)頁(yè)的原理是什么

MySQL中InnoDB數(shù)據(jù)頁(yè)的原理是什么,相信很多沒(méi)有經(jīng)驗(yàn)的人對(duì)此束手無(wú)策,為此本文總結(jié)了問(wèn)題出現(xiàn)的原因和解決方法,通過(guò)這篇文章希望你能解決這個(gè)問(wèn)題。

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

不同類型的頁(yè)簡(jiǎn)介

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

數(shù)據(jù)頁(yè)結(jié)構(gòu)的快速瀏覽

數(shù)據(jù)頁(yè)代表的這塊16KB大小的存儲(chǔ)空間可以被劃分為多個(gè)部分,不同部分有不同的功能,各個(gè)部分如圖所示:

MySQL中InnoDB數(shù)據(jù)頁(yè)的原理是什么

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

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

記錄在頁(yè)中的存儲(chǔ)

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

MySQL中InnoDB數(shù)據(jù)頁(yè)的原理是什么

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

記錄頭信息的秘密

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

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)

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

MySQL中InnoDB數(shù)據(jù)頁(yè)的原理是什么

從圖中可以看到,我們特意把記錄頭信息的5個(gè)字節(jié)的數(shù)據(jù)給標(biāo)出來(lái)了,說(shuō)明它很重要,我們?cè)俅蜗劝堰@些記錄頭信息中各個(gè)屬性的大體意思瀏覽一下(我們目前使用Compact行格式進(jìn)行演示):

名稱大小(單位:bit)描述
預(yù)留位11沒(méi)有使用
預(yù)留位21沒(méi)有使用
delete_mask1標(biāo)記該記錄是否被刪除
min_rec_mask1B+樹的每層非葉子節(jié)點(diǎn)中的最小記錄都會(huì)添加該標(biāo)記
n_owned4表示當(dāng)前記錄擁有的記錄數(shù)
heap_no13表示當(dāng)前記錄在記錄堆的位置信息
record_type3表示當(dāng)前記錄的類型,0表示普通記錄,1表示B+樹非葉節(jié)點(diǎn)記錄,2表示最小記錄,3表示最大記錄
next_record16表示下一條記錄的相對(duì)位置

由于我們現(xiàn)在主要在嘮叨記錄頭信息的作用,所以為了大家理解上的方便,我們只在page_demo表的行格式演示圖中畫出有關(guān)的頭信息屬性以及c1、c2c3列的信息(其他信息沒(méi)畫不代表它們不存在啊,只是為了理解上的方便在圖中省略了~),簡(jiǎn)化后的行格式示意圖就是這樣:

MySQL中InnoDB數(shù)據(jù)頁(yè)的原理是什么

下邊我們?cè)囍?code>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

為了方便大家分析這些記錄在頁(yè)User Records部分中是怎么表示的,我把記錄中頭信息和實(shí)際的列數(shù)據(jù)都用十進(jìn)制表示出來(lái)了(其實(shí)是一堆二進(jìn)制位),所以這些記錄的示意圖就是:

MySQL中InnoDB數(shù)據(jù)頁(yè)的原理是什么

看這個(gè)圖的時(shí)候需要注意一下,各條記錄在User Records中存儲(chǔ)的時(shí)候并沒(méi)有空隙,這里只是為了大家觀看方便才把每條記錄單獨(dú)畫在一行中。我們對(duì)照著這個(gè)圖來(lái)看看記錄頭信息中的各個(gè)屬性是啥意思:

  • delete_mask

    這個(gè)屬性標(biāo)記著當(dāng)前記錄是否被刪除,占用1個(gè)二進(jìn)制位,值為0的時(shí)候代表記錄并沒(méi)有被刪除,為1的時(shí)候代表記錄被刪除掉了。

    啥?被刪除的記錄還在頁(yè)中么?是的,擺在臺(tái)面上的和背地里做的可能大相徑庭,你以為它刪除了,可它還在真實(shí)的磁盤上[攤手](忽然想起冠?!?。這些被刪除的記錄之所以不立即從磁盤上移除,是因?yàn)橐瞥鼈冎蟀哑渌挠涗浽诖疟P上重新排列需要性能消耗,所以只是打一個(gè)刪除標(biāo)記而已,所有被刪除掉的記錄都會(huì)組成一個(gè)所謂的垃圾鏈表,在這個(gè)鏈表中的記錄占用的空間稱之為所謂的可重用空間,之后如果有新記錄插入到表中的話,可能把這些被刪除的記錄占用的存儲(chǔ)空間覆蓋掉。

  • min_rec_mask

    B+樹的每層非葉子節(jié)點(diǎn)中的最小記錄都會(huì)添加該標(biāo)記,什么是個(gè)B+樹?什么是個(gè)非葉子節(jié)點(diǎn)?好吧,等會(huì)再聊這個(gè)問(wèn)題。反正我們自己插入的四條記錄的min_rec_mask值都是0,意味著它們都不是B+樹的非葉子節(jié)點(diǎn)中的最小記錄。

  • n_owned

    這個(gè)暫時(shí)保密,稍后它是主角~

  • heap_no

    這個(gè)屬性表示當(dāng)前記錄在本頁(yè)中的位置,從圖中可以看出來(lái),我們插入的4條記錄在本頁(yè)中的位置分別是:2、3、45。是不是少了點(diǎn)啥?是的,怎么不見(jiàn)heap_no值為01的記錄呢?

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

    是的,記錄也可以比大小,對(duì)于一條完整的記錄來(lái)說(shuō),比較記錄的大小就是比較主鍵的大小。比方說(shuō)我們插入的4行記錄的主鍵值分別是:1、23、4,這也就意味著這4條記錄的大小從小到大依次遞增。

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

    MySQL中InnoDB數(shù)據(jù)頁(yè)的原理是什么

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

    MySQL中InnoDB數(shù)據(jù)頁(yè)的原理是什么

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

  • record_type

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

    至于record_type1的情況,我們之后在說(shuō)索引的時(shí)候會(huì)重點(diǎn)強(qiáng)調(diào)的。

  • next_record

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

    MySQL中InnoDB數(shù)據(jù)頁(yè)的原理是什么

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

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

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

    MySQL中InnoDB數(shù)據(jù)頁(yè)的原理是什么

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

    所以,不論我們?cè)趺磳?duì)頁(yè)中的記錄做增刪改操作,InnoDB始終會(huì)維護(hù)一條記錄的單鏈表,鏈表中的各個(gè)節(jié)點(diǎn)是按照主鍵值由小到大的順序連接起來(lái)的。

    • 第2條記錄并沒(méi)有從存儲(chǔ)空間中移除,而是把該條記錄的delete_mask值設(shè)置為1。

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

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

    • 還有一點(diǎn)你可能忽略了,就是最大記錄n_owned值從5變成了4,關(guān)于這一點(diǎn)的變化我們稍后會(huì)詳細(xì)說(shuō)明的。

再來(lái)看一個(gè)有意思的事情,因?yàn)橹麈I值為2的記錄被我們刪掉了,但是存儲(chǔ)空間卻沒(méi)有回收,如果我們?cè)俅伟堰@條記錄插入到表中,會(huì)發(fā)生什么事呢?

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

我們看一下記錄的存儲(chǔ)情況:

MySQL中InnoDB數(shù)據(jù)頁(yè)的原理是什么

從圖中可以看到,InnoDB并沒(méi)有因?yàn)樾掠涗浀牟迦攵鵀樗暾?qǐng)新的存儲(chǔ)空間,而是直接復(fù)用了原來(lái)被刪除記錄的存儲(chǔ)空間。

Page Directory(頁(yè)目錄)

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

SELECT * FROM page_demo WHERE c1 = 3;

最笨的辦法:從Infimum記錄(最小記錄)開始,沿著鏈表一直往后找,總有一天會(huì)找到(或者找不到[攤手]),在找的時(shí)候還能投機(jī)取巧,因?yàn)殒湵碇懈鱾€(gè)記錄的值是按照從小到大順序排列的,所以當(dāng)鏈表的某個(gè)節(jié)點(diǎn)代表的記錄的主鍵值大于你想要查找的主鍵值時(shí),你就可以停止查找了,因?yàn)樵摴?jié)點(diǎn)后邊的節(jié)點(diǎn)的主鍵值依次遞增。

這個(gè)方法在頁(yè)中存儲(chǔ)的記錄數(shù)量比較少的情況用起來(lái)也沒(méi)啥問(wèn)題,比方說(shuō)現(xiàn)在我們的表里只有4條自己插入的記錄,所以最多找4次就可以把所有記錄都遍歷一遍,但是如果一個(gè)頁(yè)中存儲(chǔ)了非常多的記錄,這么查找對(duì)性能來(lái)說(shuō)還是有損耗的,所以我們說(shuō)這種遍歷查找這是一個(gè)辦法。但是設(shè)計(jì)InnoDB的大叔們是什么人,他們能用這么笨的辦法么,當(dāng)然是要設(shè)計(jì)一種更6的查找方式嘍,他們從書的目錄中找到了靈感。

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

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

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

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

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

MySQL中InnoDB數(shù)據(jù)頁(yè)的原理是什么

從這個(gè)圖中我們需要注意這么幾點(diǎn):

  • 現(xiàn)在頁(yè)目錄部分中有兩個(gè)槽,也就意味著我們的記錄被分成了兩個(gè)組,槽1中的值是112,代表最大記錄的地址偏移量(就是從頁(yè)面的0字節(jié)開始數(shù),數(shù)112個(gè)字節(jié));槽0中的值是99,代表最小記錄的地址偏移量。

  • 注意最小和最大記錄的頭信息中的n_owned屬性

    • 最小記錄的n_owned值為1,這就代表著以最小記錄結(jié)尾的這個(gè)分組中只有1條記錄,也就是最小記錄本身。

    • 最大記錄的n_owned值為5,這就代表著以最大記錄結(jié)尾的這個(gè)分組中只有5條記錄,包括最大記錄本身還有我們自己插入的4條記錄。

99112這樣的地址偏移量很不直觀,我們用箭頭指向的方式替代數(shù)字,這樣更易于我們理解,所以修改后的示意圖就是這樣:

MySQL中InnoDB數(shù)據(jù)頁(yè)的原理是什么

哎呀,咋看上去怪怪的,這么亂的圖對(duì)于我這個(gè)強(qiáng)迫癥真是不能忍,那我們就暫時(shí)不管各條記錄在存儲(chǔ)設(shè)備上的排列方式了,單純從邏輯上看一下這些記錄和頁(yè)目錄的關(guān)系:

MySQL中InnoDB數(shù)據(jù)頁(yè)的原理是什么

這樣看就順眼多了嘛!為什么最小記錄的n_owned值為1,而最大記錄的n_owned值為5呢,這里頭有什么貓膩么?

是的,設(shè)計(jì)InnoDB的大叔們對(duì)每個(gè)分組中的記錄條數(shù)是有規(guī)定的:對(duì)于最小記錄所在的分組只能有 1 條記錄,最大記錄所在的分組擁有的記錄條數(shù)只能在 1~8 條之間,剩下的分組中記錄的條數(shù)范圍只能在是 4~8 條之間。所以分組是按照下邊的步驟進(jìn)行的:

  • 初始情況下一個(gè)數(shù)據(jù)頁(yè)里只有最小記錄和最大記錄兩條記錄,它們分屬于兩個(gè)分組。

  • 之后每插入一條記錄,都會(huì)從頁(yè)目錄中找到主鍵值比本記錄的主鍵值大并且差值最小的槽,然后把該槽對(duì)應(yīng)的記錄的n_owned值加1,表示本組內(nèi)又添加了一條記錄,直到該組中的記錄數(shù)等于8個(gè)。

  • 在一個(gè)組中的記錄數(shù)等于8個(gè)后再插入一條記錄時(shí),會(huì)將組中的記錄拆分成兩個(gè)組,一個(gè)組中4條記錄,另一個(gè)5條記錄。這個(gè)過(guò)程會(huì)在頁(yè)目錄中新增一個(gè)來(lái)記錄這個(gè)新增分組中最大的那條記錄的偏移量。

由于現(xiàn)在page_demo表中的記錄太少,無(wú)法演示添加了頁(yè)目錄之后加快查找速度的過(guò)程,所以再往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條記錄,現(xiàn)在頁(yè)里邊就一共有18條記錄了(包括最小和最大記錄),這些記錄被分成了5個(gè)組,如圖所示:

MySQL中InnoDB數(shù)據(jù)頁(yè)的原理是什么

因?yàn)榘?6條記錄的全部信息都畫在一張圖里太占地方,讓人眼花繚亂的,所以只保留了用戶記錄頭信息中的n_ownednext_record屬性,也省略了各個(gè)記錄之間的箭頭,我沒(méi)畫不等于沒(méi)有?。‖F(xiàn)在看怎么從這個(gè)頁(yè)目錄中查找記錄。因?yàn)楦鱾€(gè)槽代表的記錄的主鍵值都是從小到大排序的,所以我們可以使用所謂的二分法來(lái)進(jìn)行快速查找。5個(gè)槽的編號(hào)分別是:01、2、34,所以初始情況下最低的槽就是low=0,最高的槽就是high=4。比方說(shuō)我們想找主鍵值為6的記錄,過(guò)程是這樣的:

  1. 計(jì)算中間槽的位置:(0+4)/2=2,所以查看槽2對(duì)應(yīng)記錄的主鍵值為8,又因?yàn)?code>8 > 6,所以設(shè)置high=2,low保持不變。

  2. 重新計(jì)算中間槽的位置:(0+2)/2=1,所以查看槽1對(duì)應(yīng)的主鍵值為4,又因?yàn)?code>4 < 6,所以設(shè)置low=1,high保持不變。

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

所以在一個(gè)數(shù)據(jù)頁(yè)中查找指定主鍵值的記錄的過(guò)程分為兩步:

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

  2. 通過(guò)記錄的next_record屬性遍歷該槽所在的組中的各個(gè)記錄。

Page Header(頁(yè)面頭部)

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

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

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

  • PAGE_DIRECTION

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

  • PAGE_N_DIRECTION

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

至于我們沒(méi)提到的那些屬性,我沒(méi)說(shuō)是因?yàn)楝F(xiàn)在不需要大家知道。不要著急,當(dāng)我們學(xué)完了后邊的內(nèi)容,你再回頭看,一切都是那么清晰。

File Header(文件頭部)

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

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

對(duì)照著這個(gè)表格,我們看幾個(gè)目前比較重要的部分:

  • FIL_PAGE_SPACE_OR_CHKSUM

    這個(gè)代表當(dāng)前頁(yè)面的校驗(yàn)和(checksum)。啥是個(gè)校驗(yàn)和?就是對(duì)于一個(gè)很長(zhǎng)很長(zhǎng)的字節(jié)串來(lái)說(shuō),我們會(huì)通過(guò)某種算法來(lái)計(jì)算一個(gè)比較短的值來(lái)代表這個(gè)很長(zhǎng)的字節(jié)串,這個(gè)比較短的值就稱為校驗(yàn)和。這樣在比較兩個(gè)很長(zhǎng)的字節(jié)串之前先比較這兩個(gè)長(zhǎng)字節(jié)串的校驗(yàn)和,如果校驗(yàn)和都不一樣兩個(gè)長(zhǎng)字節(jié)串肯定是不同的,所以省去了直接比較兩個(gè)比較長(zhǎng)的字節(jié)串的時(shí)間損耗。

  • FIL_PAGE_OFFSET

    每一個(gè)頁(yè)都有一個(gè)單獨(dú)的頁(yè)號(hào),就跟你的身份證號(hào)碼一樣,InnoDB通過(guò)頁(yè)號(hào)來(lái)可以唯一定位一個(gè)頁(yè)。

  • FIL_PAGE_TYPE

    這個(gè)代表當(dāng)前頁(yè)的類型,我們前邊說(shuō)過(guò),InnoDB為了不同的目的而把頁(yè)分為不同的類型,我們上邊介紹的其實(shí)都是存儲(chǔ)記錄的數(shù)據(jù)頁(yè),其實(shí)還有很多別的類型的頁(yè),具體如下表:

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

    我們存放記錄的數(shù)據(jù)頁(yè)的類型其實(shí)是FIL_PAGE_INDEX,也就是所謂的索引頁(yè)。至于啥是個(gè)索引,且聽(tīng)下回分解~

  • FIL_PAGE_PREVFIL_PAGE_NEXT

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

    MySQL中InnoDB數(shù)據(jù)頁(yè)的原理是什么

關(guān)于File Header的其他屬性我們暫時(shí)用不到,等用到的時(shí)候再提哈~

File Trailer

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

  • 前4個(gè)字節(jié)代表頁(yè)的校驗(yàn)和

    這個(gè)部分是和File Header中的校驗(yàn)和相對(duì)應(yīng)的。每當(dāng)一個(gè)頁(yè)面在內(nèi)存中修改了,在同步之前就要把它的校驗(yàn)和算出來(lái),因?yàn)?code>File Header在頁(yè)面的前邊,所以校驗(yàn)和會(huì)被首先同步到磁盤,當(dāng)完全寫完時(shí),校驗(yàn)和也會(huì)被寫到頁(yè)的尾部,如果完全同步成功,則頁(yè)的首部和尾部的校驗(yàn)和應(yīng)該是一致的。如果寫了一半兒斷電了,那么在File Header中的校驗(yàn)和就代表著已經(jīng)修改過(guò)的頁(yè),而在File Trailer中的校驗(yàn)和代表著原先的頁(yè),二者不同則意味著同步中間出了錯(cuò)。

  • 后4個(gè)字節(jié)代表頁(yè)面被最后修改時(shí)對(duì)應(yīng)的日志序列位置(LSN)

    這個(gè)部分也是為了校驗(yàn)頁(yè)的完整性的,只不過(guò)我們目前還沒(méi)說(shuō)LSN是個(gè)什么意思,所以大家可以先不用管這個(gè)屬性。

這個(gè)File TrailerFile Header類似,都是所有類型的頁(yè)通用的。

總結(jié)

  1. InnoDB為了不同的目的而設(shè)計(jì)了不同類型的頁(yè),我們把用于存放記錄的頁(yè)叫做數(shù)據(jù)頁(yè)。

  2. 一個(gè)數(shù)據(jù)頁(yè)可以被大致劃分為7個(gè)部分,分別是

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

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

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

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

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

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

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

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

  4. InnoDB會(huì)把頁(yè)中的記錄劃分為若干個(gè)組,每個(gè)組的最后一個(gè)記錄的地址偏移量作為一個(gè),存放在Page Directory中,所以在一個(gè)頁(yè)中根據(jù)主鍵查找記錄是非常快的,分為兩步:

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

    • 通過(guò)記錄的next_record屬性遍歷該槽所在的組中的各個(gè)記錄。

  5. 每個(gè)數(shù)據(jù)頁(yè)的File Header部分都有上一個(gè)和下一個(gè)頁(yè)的編號(hào),所以所有的數(shù)據(jù)頁(yè)會(huì)組成一個(gè)雙鏈表。

  6. 為保證從內(nèi)存中同步到磁盤的頁(yè)的完整性,在頁(yè)的首部和尾部都會(huì)存儲(chǔ)頁(yè)中數(shù)據(jù)的校驗(yàn)和和頁(yè)面最后修改時(shí)對(duì)應(yīng)的LSN值,如果首部和尾部的校驗(yàn)和和LSN值校驗(yàn)不成功的話,就說(shuō)明同步過(guò)程出現(xiàn)了問(wèn)題。

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

新聞標(biāo)題:MySQL中InnoDB數(shù)據(jù)頁(yè)的原理是什么
文章地址:http://www.rwnh.cn/article42/jdgehc.html

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

廣告

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

成都網(wǎng)頁(yè)設(shè)計(jì)公司
威海市| 闵行区| 南丰县| 威宁| 朔州市| 环江| 莆田市| 鸡泽县| 汶川县| 江源县| 普兰店市| 台中县| 凤冈县| 南汇区| 迁西县| 株洲市| 勐海县| 上高县| 海南省| 金沙县| 陆丰市| 新乡市| 定日县| 蒙城县| 长宁县| 祁阳县| 灵丘县| 九龙坡区| 西贡区| 台安县| 桐乡市| 简阳市| 塔河县| 琼结县| 古丈县| 本溪市| 安阳县| 五大连池市| 沂水县| 思南县| 台前县|