欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

一文搞懂MySQL索引頁(yè)結(jié)構(gòu)

 更新時(shí)間:2022年02月28日 11:14:37   作者:程序員小潘  
本文主要介紹了MySQL索引頁(yè)結(jié)構(gòu),文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下

1. 前言

「頁(yè)」是InnoDB管理存儲(chǔ)空間的基本單位,也是內(nèi)存和磁盤交互的基本單位。也就是說(shuō),哪怕你需要1字節(jié)的數(shù)據(jù),InnoDB也會(huì)讀取整個(gè)頁(yè)的數(shù)據(jù),下次讀取的數(shù)據(jù)如果恰巧也在這個(gè)頁(yè)里,就能命中緩存了。寫也是一樣的,寫數(shù)據(jù)前要先把頁(yè)加載到內(nèi)存,然后在內(nèi)存中修改,該頁(yè)被記為「臟頁(yè)」,臟頁(yè)淘汰之前必須刷盤。

InnoDB有很多類型的頁(yè),它們的用處也各不相同。比如:有存放undo日志的頁(yè)、有存放INODE信息的頁(yè)、有存放Change Buffer信息的頁(yè)、存放用戶記錄數(shù)據(jù)的頁(yè)等等。今天我們要聊的,就是最基礎(chǔ)也是最重要的,存放用戶記錄數(shù)據(jù)的「索引頁(yè)」。

2. 索引頁(yè)結(jié)構(gòu)

InnoDB默認(rèn)的頁(yè)大小是16KB,在初始化表空間之前可以在配置文件中進(jìn)行配置,一旦初始化完成就不可再變更了。查看頁(yè)大小的命令如下,顯示的是字節(jié)數(shù)。

SHOW VARIABLES LIKE 'innodb_page_size';

索引頁(yè)結(jié)構(gòu)如下圖所示:

image.png

索引頁(yè)由七部分組成,其中Infimum和Supremum也屬于記錄,只不過(guò)是虛擬記錄,這里為了與用戶記錄區(qū)分開(kāi),還是決定將兩者拆開(kāi)。

名稱大小描述
File Header38字節(jié)所有頁(yè)的通用文件頭信息
Page Header56字節(jié)索引頁(yè)特有的頁(yè)頭信息
Infimum+Supremum26字節(jié)頁(yè)中虛擬的最小、最大記錄
User Records變長(zhǎng)用戶記錄數(shù)據(jù)
Free Space變長(zhǎng)空閑空間
Page Directory變長(zhǎng)頁(yè)目錄,加速頁(yè)內(nèi)數(shù)據(jù)檢索效率
File Trailer8字節(jié)所有頁(yè)的通用文件尾信息,校驗(yàn)頁(yè)是否完整

2.1 File Header

File Header是所有頁(yè)都有的一個(gè)通用的結(jié)構(gòu),占用固定的38字節(jié),它記錄了頁(yè)的一些通用的狀態(tài)信息,例如:頁(yè)的頁(yè)號(hào)、Checksum、把頁(yè)串聯(lián)成雙向鏈表的指針、頁(yè)的類型等等。

名稱大小描述
FIL_PAGE_SPACE_OR_CHECKSUM4字節(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í)的LSN值
FIL_PAGE_TYPE2字節(jié)頁(yè)的類型
FIL_PAGE_FILE_FLUSH_LSN8字節(jié)僅在系統(tǒng)表空間的第1個(gè)頁(yè)中使用,代表文件至少被刷新到了對(duì)應(yīng)的LSN值
FIL_PAGE_ARCH_LOG_NO_OR_SPACE_ID4字節(jié)頁(yè)數(shù)據(jù)哪個(gè)表空間

FIL_PAGE_SPACE_OR_CHECKSUM

基于當(dāng)前頁(yè)計(jì)算出的校驗(yàn)和(Checksum),可以把它看作是哈希值,校驗(yàn)和不同,則兩個(gè)頁(yè)數(shù)據(jù)肯定不同。它的作用是InnoDB在臟頁(yè)刷盤時(shí),有可能會(huì)遇到頁(yè)刷到一半斷電的情況,頁(yè)的頭和尾部分分別記錄校驗(yàn)和,只有當(dāng)頭尾的校驗(yàn)和一致的時(shí)候,才代表磁盤上的頁(yè)是完整的,否則就是一個(gè)損壞的頁(yè)。

FIL_PAGE_OFFSET

頁(yè)號(hào),頁(yè)的唯一標(biāo)識(shí),全局遞增的數(shù)字,InnoDB通過(guò)頁(yè)號(hào)來(lái)定位唯一的一個(gè)頁(yè)。4字節(jié)存儲(chǔ),意味著一個(gè)表空間最多可以有232個(gè)頁(yè),按照一個(gè)頁(yè)16KB計(jì)算,則一個(gè)表空間最多支持64TB的數(shù)據(jù)。

FIL_PAGE_PREV & FIL_PAGE_NEXT

一個(gè)頁(yè)大小才16KB,一張表數(shù)據(jù)其實(shí)是由N多個(gè)頁(yè)構(gòu)成的,頁(yè)與頁(yè)之間在物理上可以是不連續(xù)的,但是邏輯上要連續(xù),F(xiàn)IL_PAGE_PREV和FIL_PAGE_NEXT分別指向當(dāng)前頁(yè)的上一個(gè)頁(yè)和下一個(gè)頁(yè)的頁(yè)號(hào),通過(guò)這兩個(gè)指針將索引頁(yè)串聯(lián)成了一個(gè)雙向鏈表。記錄與記錄之間是單向的,頁(yè)與頁(yè)之間是雙向的!

FIL_PAGE_LSN

頁(yè)面最后被修改時(shí),對(duì)應(yīng)的LSN值。LSN的全稱是Log Sequence Number,日志序列號(hào)。它是一個(gè)遞增的數(shù)字,和事務(wù)相關(guān),這里不作贅述。

FIL_PAGE_TYPE

當(dāng)前頁(yè)的類型,InnoDB為了不同的目的設(shè)計(jì)了很多不同類型的頁(yè),索引頁(yè)的固定值是0x45BF。

FIL_PAGE_FILE_FLUSH_LSN

僅在第1個(gè)頁(yè)中使用,用來(lái)判斷數(shù)據(jù)庫(kù)是正常關(guān)閉還是異常宕機(jī)。

FIL_PAGE_ARCH_LOG_NO_OR_SPACE_ID

僅記錄當(dāng)前頁(yè)數(shù)據(jù)哪個(gè)表空間。

2.2 Page Header

Page Header是索引頁(yè)特有的結(jié)構(gòu),占用固定的56字節(jié),它記錄了索引頁(yè)中記錄相關(guān)的狀態(tài)信息。

名稱大小描述
PAGE_N_DlR_SLOTS2字節(jié)頁(yè)目錄中的槽數(shù)量
PAGE_HEAP_TOP2字節(jié)未使用的空間最小地址,User Records和Free Space分界點(diǎn)
PAGE_N_HEAP2字節(jié)本頁(yè)中的記錄的數(shù)量(包括虛擬記錄和刪除記錄)
PAGE_FREE2字節(jié)第一個(gè)刪除的記錄地址,后續(xù)刪除的記錄會(huì)形成鏈表。
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ù)量(不包括虛擬記錄和刪除記錄)
PAGE_MAX_TRX_ID8字節(jié)修改當(dāng)前頁(yè)的最大事務(wù)ID,僅在二級(jí)索引中使用
PAGE_LEVEL2字節(jié)當(dāng)前頁(yè)在B+樹(shù)中所處的層級(jí)
PAGE_INDEX_ID8字節(jié)索引ID,表示當(dāng)前頁(yè)屬于哪個(gè)索引
PAGE_BTR_SEG_LEAF10字節(jié)B+樹(shù)葉子段的頭部信息,僅在B+樹(shù)的Root頁(yè)定義
PAGE_BTR_SEG_TOP10字節(jié)B+樹(shù)非葉子段的頭部信息,僅在B+樹(shù)的Root頁(yè)定義

不用每個(gè)屬性都了解,我們挑幾個(gè)比較重要的看看。

PAGE_N_DlR_SLOTS

一個(gè)頁(yè)內(nèi)可能有上千條記錄,挨個(gè)遍歷的話效率太慢了。為了提高頁(yè)內(nèi)記錄的檢索效率,InnoDB將頁(yè)內(nèi)的記錄劃分為多個(gè)組,組里最大的那條記錄相較于頁(yè)的地址偏移量會(huì)記錄到「Page Directory」部分,每個(gè)組都對(duì)應(yīng)一個(gè)槽,槽的大小是固定的2字節(jié)。該屬性記錄的就是頁(yè)內(nèi)槽的數(shù)量。

PAGE_HEAP_TOP

Free Space的起始位置,它是User Records和Free Space分界點(diǎn)。一個(gè)全新的頁(yè)一開(kāi)始是沒(méi)有User Records部分的,每插入一條記錄,都要向Free Space申請(qǐng)空間,F(xiàn)ree Space耗盡就代表頁(yè)滿了。

PAGE_FREE

DELETE命令刪除記錄時(shí),InnoDB并不會(huì)真的將記錄從磁盤中刪除,而是在記錄的頭信息里打個(gè)標(biāo)記,然后將其加入到「垃圾鏈表」中。PAGE_FREE指向的就是垃圾鏈表的表頭記錄。后面刪除的記錄,也會(huì)自動(dòng)加入到鏈表里。

PAGE_DIRECTION & PAGE_N_DIRECTION

PAGE_DIRECTION表示最后一條記錄插入的方向,比上一條記錄值大則記為右邊,反之則是左邊。PAGE_N_DIRECTION表示同一方向連續(xù)插入的記錄數(shù),方向變了該值就會(huì)重置。

PAGE_LEVEL

InnoDB組織數(shù)據(jù)的形式就是B+樹(shù),樹(shù)中的節(jié)點(diǎn)就是索引頁(yè),PAGE_LEVEL代表當(dāng)前頁(yè)在B+樹(shù)中所處的層級(jí)。InnoDB規(guī)定,葉子節(jié)點(diǎn)層級(jí)為0,然后向上遞增。

2.3 User Records

Infimum和Supremum也屬于記錄,只是為了與用戶記錄區(qū)分開(kāi)才劃分成了兩部分,我們先看User Records。

用戶記錄存放在User Records部分,一個(gè)全新的頁(yè)一開(kāi)始全是Free Space,是沒(méi)有User Records部分的。每插入一條記錄都需要到Free Space申請(qǐng)一塊空間,并將其劃分到User Records用來(lái)存放用戶記錄。當(dāng)Free Space耗盡也就代表當(dāng)前頁(yè)已經(jīng)用完了,再有新記錄需要插入,就需要申請(qǐng)一個(gè)新的頁(yè)了。

image.png

還記得MySQL的行格式嗎?它決定了記錄在磁盤里的存儲(chǔ)格式。以COMPACT為例,存儲(chǔ)格式如下圖:

image.png

記錄頭信息里的字段比較關(guān)鍵,以防大家忘記,我這里再貼一下:

名稱大小(Bit)說(shuō)明
預(yù)留位11沒(méi)有使用
預(yù)留位21沒(méi)有使用
deleted_flag1記錄刪除標(biāo)記
min_rec_flag1B+樹(shù)非葉子節(jié)點(diǎn)的最小目錄項(xiàng)標(biāo)記
n_owned4同一頁(yè)內(nèi)同一組里最大的記錄會(huì)記錄組里的記錄數(shù)量,其余記錄該值為0
heap_no13當(dāng)前記錄在頁(yè)面堆里的相對(duì)位置
record_type3記錄類型。0:普通記錄,1:B+樹(shù)非葉子節(jié)點(diǎn)目錄項(xiàng)記錄,2:Infimum記錄,3:Supremum記錄.
next_record16下一條記錄的相對(duì)位置

記錄頭信息的最后2字節(jié)用來(lái)連接下一條記錄,將頁(yè)內(nèi)所有記錄串聯(lián)成一個(gè)單向鏈表。所以我們隱藏變長(zhǎng)字段長(zhǎng)度列表和NULL值列表,記錄的格式應(yīng)該是這樣的:

image.png

記錄是怎么排序的?
我們已經(jīng)知道,頁(yè)內(nèi)的記錄會(huì)自動(dòng)串聯(lián)成一個(gè)單向鏈表。那這個(gè)鏈表的編排順序是什么呢?是按照記錄的插入時(shí)間排序的嗎?其實(shí)不是的,如果表有主鍵,會(huì)根據(jù)主鍵排序;沒(méi)主鍵有唯一非空索引,會(huì)根據(jù)該索引排序;兩者都沒(méi)有,InnoDB會(huì)自動(dòng)生成一個(gè)row_id列并根據(jù)該列進(jìn)行排序。

若無(wú)特殊說(shuō)明,本文均假定表有主鍵。

2.4 Infimum & Supremum

Infimum和Supremum是索引頁(yè)內(nèi)的兩條虛擬記錄,InnoDB規(guī)定所有索引頁(yè)都會(huì)有這兩條記錄,而且所有的用戶記錄都比Infimum大,都比Supremum小。
記錄頭信息里的heap_no代表記錄在堆里的相對(duì)位置,該值越小代表記錄越靠前。細(xì)心的同學(xué)會(huì)發(fā)現(xiàn),上圖中的用戶記錄heap_no值是從2開(kāi)始的,那0和1呢?不說(shuō)你也肯定猜到了,就是被Infimum和Supremum占用了。Infimum和Supremum的heap_no值分別是0和1,它倆在所有用戶記錄的最前面。

Infimum和Supremum結(jié)構(gòu)非常的簡(jiǎn)單,和用戶記錄一樣也有頭信息,真實(shí)數(shù)據(jù)部分是固定的字符串,如下圖所示:

image.png

我們把這兩條虛擬記錄也加入到記錄里面,完整的結(jié)構(gòu)就是下面這樣的:

image.png

Supremum記錄的next_record屬性為0,代表它已經(jīng)沒(méi)有下一條記錄了。

2.5 Page Directory

Free Space沒(méi)什么好說(shuō)的,就是一塊未被使用的空閑空間。

Page Directory也叫作「頁(yè)目錄」,它的目的是提高頁(yè)內(nèi)記錄的檢索效率。相較于一張表幾千萬(wàn)的記錄來(lái)說(shuō),一個(gè)頁(yè)內(nèi)幾百上千條記錄已經(jīng)是很少很少了??杉幢闳绱耍灿袔装偕锨l啊,如果頁(yè)內(nèi)檢索記錄只能挨個(gè)遍歷的話,那也太低效了。別忘了,頁(yè)內(nèi)的記錄是根據(jù)索引值排好序的,我們可以巧用「二分法」來(lái)快速查找。

具體做法是:將頁(yè)內(nèi)所有非刪除的記錄劃分為N個(gè)組,每個(gè)組里最后一條記錄(即主鍵最大的記錄)稱作“大哥”,其余記錄是“小弟”,“大哥”的n_owned屬性記錄了組內(nèi)的記錄數(shù)量。將“大哥”在頁(yè)內(nèi)的地址偏移量提取出來(lái),按順序依次從File Trailer部分往前寫,每個(gè)地址偏移量占用2字節(jié),稱作一個(gè)「槽」,Page Directory就是由這些槽構(gòu)成的。
InnoDB對(duì)于分組內(nèi)的記錄數(shù)量有一些規(guī)定:

  • Infimum記錄所在分組,只能有一條記錄。
  • Supremum記錄所在分組,允許有1~8條記錄。
  • 其余分組,允許有4~8條記錄。

由此可見(jiàn),一個(gè)組里最多有8條記錄,只要通過(guò)二分法快速定位到組,InnoDB也只需要遍歷這8條記錄,相較于遍歷頁(yè)內(nèi)所有記錄,效率要高的多。

image.png

2.6 File Trailer

File Trailer是所有頁(yè)都有的通用結(jié)構(gòu),占用固定的8字節(jié),它的主要作用就是為了校驗(yàn)頁(yè)的完整性。磁盤的速度實(shí)在是太慢了,InnoDB不會(huì)每次寫點(diǎn)數(shù)據(jù)都直接刷新到磁盤上,那樣MySQL會(huì)慢死。而是將頁(yè)作為刷盤的基本單位,數(shù)據(jù)修改時(shí),先改內(nèi)存里的頁(yè),稍后再將整個(gè)頁(yè)的數(shù)據(jù)一次性刷新到磁盤里。但是這會(huì)帶來(lái)一個(gè)問(wèn)題,一個(gè)頁(yè)16KB,刷到第10KB的時(shí)候磁盤斷電了怎么辦?重啟后InnoDB如何判斷磁盤里的頁(yè)數(shù)據(jù)是完整的?
?

InnoDB是這么處理的,刷盤前根據(jù)頁(yè)數(shù)據(jù)計(jì)算出一個(gè)Checksum,在頁(yè)頭和頁(yè)尾都寫一份。頁(yè)刷盤的時(shí)候,先刷頁(yè)頭再刷頁(yè)尾,當(dāng)頭尾兩個(gè)Checksum值一致的時(shí)候,代表磁盤里的頁(yè)是完整的,否則就表示頁(yè)頭刷了頁(yè)尾沒(méi)刷,那肯定是刷到一半出錯(cuò)了。

大小說(shuō)明
4字節(jié)頁(yè)的校驗(yàn)和Checksum
4字節(jié)頁(yè)最后被修改時(shí)對(duì)應(yīng)的LSN的后4個(gè)字節(jié),正常情況下應(yīng)該與File Header里的FIL_PAGE_LSN的后4個(gè)字節(jié)相同。

3. 總結(jié)

頁(yè)是InnoDB存取數(shù)據(jù)的基本單位,默認(rèn)頁(yè)大小是16KB,InnoDB為了不同的目的設(shè)計(jì)了很多不同類型的頁(yè),本文重點(diǎn)分析了存放用戶記錄的索引頁(yè)。頁(yè)的頭尾部分File Header和File Trailer是所有頁(yè)都有的一個(gè)通用結(jié)構(gòu),它們記錄了頁(yè)的一些通用狀態(tài)信息,和Checksum用來(lái)驗(yàn)證頁(yè)的完整性。Page Header是索引頁(yè)特有的結(jié)構(gòu),它記錄了頁(yè)內(nèi)用戶記錄相關(guān)的狀態(tài)信息。User Records部分用來(lái)存放用戶記錄。另外,由于頁(yè)內(nèi)的記錄數(shù)量也不少,為了提高頁(yè)內(nèi)記錄的檢索效率,InnoDB在索引頁(yè)中加入了Page Directory,它通過(guò)將記錄分組,將組里最大的記錄的地址偏移量形成一個(gè)個(gè)槽,Page Directory就是由這些槽構(gòu)成的。檢索數(shù)據(jù)時(shí),使用二分法快速定位到槽所在的組,就可以避免遍歷所有組的記錄了。

到此這篇關(guān)于MySQL索引頁(yè)結(jié)構(gòu)的文章就介紹到這了,更多相關(guān)MySQL索引頁(yè)結(jié)構(gòu)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評(píng)論