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

Mysql行格式索引頁詳解

 更新時(shí)間:2024年03月25日 10:32:18   作者:子♂衿  
這篇文章主要介紹了Mysql行格式索引頁,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教

本篇文章以InnoDB存儲(chǔ)引擎為例,主要講兩個(gè)大知識(shí)點(diǎn)。

  • 行格式
  • 索引頁(也就是我們說的數(shù)據(jù)頁)

行格式

先想一個(gè)問題,MySql是什么?是一個(gè)數(shù)據(jù)庫系統(tǒng)?用來干什么的? 我們先來簡(jiǎn)化一下MySql,MySql其實(shí)也是一個(gè)軟件,它只是給我們管理數(shù)據(jù),然后方便我們對(duì)數(shù)據(jù)進(jìn)行增刪改查,就像Spring一樣,它給我們管理Java對(duì)象,然后方便我們使用。

那既然是給我們管理數(shù)據(jù),那肯定就會(huì)將我們的數(shù)據(jù)保存起來,這樣就會(huì)涉及到一個(gè)問題?如何保存這樣數(shù)據(jù)?以怎樣的格式保存這些數(shù)據(jù)? 為了方便并且高效的管理數(shù)據(jù)MySql肯定會(huì)設(shè)計(jì)一套自己的方案來管理。我們來以行格式開始講起。

行格式是什么? 我們平時(shí)向表里面插入一條記錄,這條記錄以什么的格式進(jìn)行保存,就是我們要講的東西,InnoDB主要有以下四種行格式

  • COMPACT 本文主要講的行格式
  • REDUNDANT 是很古老的行格式了, MySQL 5.0 版本之前用的行格式
  • DYNAMIC InnoDB的默認(rèn)行格式,理解了COMPACT 行格式就會(huì)理解這個(gè),只有一小點(diǎn)不同
  • COMPRESSED 我也了解很少,所以不講

先來看看COMPACT行格式都包含哪些信息

當(dāng)我們插入一條記錄的時(shí)候MySql并不是只保存了我們插入的數(shù)據(jù),它還會(huì)記錄很多額外的信息,這些額外的信息都是為了更好的幫我們管理數(shù)據(jù)。

變長(zhǎng)字段長(zhǎng)度列表

可能我們經(jīng)常聽到char類型是指定多少長(zhǎng)度就要占用多少長(zhǎng)度,比如我們指定char(100)個(gè)長(zhǎng)度,但是我們實(shí)際只存儲(chǔ)了一個(gè)“1”這個(gè)時(shí)候char也是占用100個(gè)長(zhǎng)度的空間的,所以這種類型我們用的相對(duì)比較少,我們常用varchar,而這個(gè)varchar就是變長(zhǎng)的,我們指定的長(zhǎng)度只是表示最大能存放多少,實(shí)際占用的空間是你存了多少就占用多少空間,注意這里有個(gè) 編碼的問題,當(dāng)我們指定編碼格式為 ascii時(shí)一個(gè)字符占用一個(gè)字節(jié),當(dāng)我們指定為utf8時(shí)占用3個(gè)字節(jié),當(dāng)指定utf8mb4時(shí)則占用四個(gè)字節(jié),而varchar指定的是能存多少個(gè)已某種方式編碼的字符,而不是多少個(gè)字節(jié),假設(shè)我們使用utf8編碼,使用varchar(100),則這個(gè)字段最多能存儲(chǔ)100個(gè)字符,占用的空間最大是 100 * 3個(gè)字節(jié)

那這個(gè)變長(zhǎng)字段長(zhǎng)度列表有什么用呢?

我們知道varchar是變長(zhǎng)的了,你給我多少個(gè)字符我就存儲(chǔ)多少個(gè),這樣確實(shí)是省了空間了,但是我們很快就發(fā)現(xiàn)一個(gè)問題,我們讀取數(shù)據(jù)的時(shí)候怎么辦,我們?cè)撟x多少?所以必須有個(gè)地方記錄下來,每個(gè)字段實(shí)際占用多少空間,這樣我們讀的時(shí)候就能順利讀取,而這個(gè)長(zhǎng)度就存放在記錄在 一條記錄開頭。

每一個(gè)varchar類型的字段并且實(shí)際值不為NULL的字段的值的長(zhǎng)度都會(huì)保存在這個(gè)列表里,每個(gè)字段的長(zhǎng)度是逆序存放在這個(gè)列表里的(為啥要逆序放?后面講)

上面我們存儲(chǔ)的數(shù)據(jù)比較少用一個(gè)字節(jié)就能記錄下,在現(xiàn)實(shí)中我們可能指定一個(gè)非常大的長(zhǎng)度,這樣的話用一個(gè)字節(jié)可能就記錄不下了。所以MySql肯定有針對(duì)此的一套規(guī)則,理解這套規(guī)則我們需要區(qū)分開 varchar(10)這個(gè)10指的是最多能存儲(chǔ)10個(gè)字符,并不是10個(gè)字節(jié),當(dāng)我們使用utf8編碼時(shí)一個(gè)字符占用3個(gè)字節(jié),10個(gè)字符表示最多能占用 3 * 10=30個(gè)字節(jié),所以當(dāng)我們的字段最大能存儲(chǔ)的字節(jié)不超過255時(shí),注意是字節(jié)!字節(jié)!字節(jié)!使用一個(gè)字節(jié)就能記錄這個(gè)字段的長(zhǎng)度,而當(dāng)這個(gè)字段的最大長(zhǎng)度超過255字節(jié)時(shí)則分為兩種情況

  • 情況一:當(dāng)實(shí)際占用的字節(jié)小于等于127字節(jié)時(shí)使用一個(gè)字節(jié)記錄這個(gè)長(zhǎng)度
  • 情況二:當(dāng)實(shí)際占用的字節(jié)大于127時(shí)使用兩個(gè)字節(jié)記錄這個(gè)長(zhǎng)度

NULL值列表

大多數(shù)情況下我們的列都會(huì)允許為NULL,那這個(gè)NULL在MySql是如何存儲(chǔ)的呢?

MySql為那些可以為NULL的字段專門弄了一個(gè)NULL列表,該列表中用位圖的表示形式來表示某個(gè)字段是否為NULL。

假設(shè)我們有表中有三個(gè)字段,并且這三個(gè)字段都可以為NULL,那這個(gè)NULL列表就會(huì)占用一個(gè)字節(jié),一個(gè)字節(jié)有8個(gè)比特,每個(gè)比特對(duì)應(yīng)一個(gè)字段,并且也是逆序?qū)?yīng)的和上面的變長(zhǎng)字段長(zhǎng)度列表一樣,如果某個(gè)字段對(duì)應(yīng)的比特位為1則代表這個(gè)字段為NULL,反之不為NULL

NULL值列表占用的空間很少,比如我們表中有不超過8個(gè)的允許為NULL字段,這個(gè)時(shí)候使用一個(gè)字節(jié)就能記錄下,當(dāng)超過了8個(gè)就使用2個(gè)字節(jié)記錄,2個(gè)字節(jié)實(shí)際上可以記錄16個(gè)字段,即使我們只有11個(gè)允許為NULL的字段也會(huì)占用兩個(gè)字節(jié),使用0進(jìn)行對(duì)齊,如此類推當(dāng)我們超過了16個(gè)允許為NULL的字段的時(shí)候就使用3個(gè)字節(jié)或者更多來描述,總之就是一個(gè)字節(jié)可以描述8個(gè)字段,并且使用0進(jìn)行對(duì)齊。

一條記錄到底是如何存儲(chǔ)的?

現(xiàn)在我們創(chuàng)建一個(gè)表, 并且插入兩條記錄

Create table t_test(
	c1 varchar(10),
	c2 varchar(10),
	c3 char(10)
) charset=ascii;

insert into t_test(c1, c2, c3) values('aaa', 'bb', 'c');
insert into t_test(c1, c2, c3) values('d', NULL, NULL)

講了這么多我們來一個(gè)圖看看這兩條記錄到底是如何存儲(chǔ)的

再次強(qiáng)調(diào)變長(zhǎng)字段長(zhǎng)度列表和NULL值列表都是逆序?qū)?yīng)某個(gè)字段的

char類型的字段并不在變長(zhǎng)字段長(zhǎng)度列表中(但實(shí)際也有可能會(huì)在變長(zhǎng)字段長(zhǎng)度列表中,后面講),而且我們c3這個(gè)字段實(shí)際只存儲(chǔ)了 ‘c’ 這個(gè)值,但是MySql把我們沒有使用的空間存儲(chǔ)成了0x20(空格)

第二條數(shù)據(jù)

可以發(fā)現(xiàn)為NULL的字段并不占用實(shí)際的存儲(chǔ)空間,MySql只會(huì)存儲(chǔ)不為NULL的字段,這都得益于NULL值長(zhǎng)度列表

char的特殊情況

經(jīng)過上面的介紹我們知道char類型是定長(zhǎng)的,并且不會(huì)出現(xiàn)在變長(zhǎng)字段長(zhǎng)度列表中,但是實(shí)際可能并不如此,因?yàn)楝F(xiàn)實(shí)中我們基本不可能使用ascii編碼,而是使用utf8或者utf8mb4等變長(zhǎng)編碼,注意ascii是一個(gè)定長(zhǎng)編碼,在這個(gè)編碼的中每個(gè)字符都只占用一個(gè)字節(jié),而像utf8這種變長(zhǎng)編碼則不一定,它是占用1~3個(gè)字節(jié),所以當(dāng)我們字段的編碼是變長(zhǎng)編碼時(shí)char類型的字段也會(huì)存儲(chǔ)到變長(zhǎng)字段長(zhǎng)度列表中。

那既然都會(huì)存儲(chǔ)到變長(zhǎng)字段長(zhǎng)度列表中它與varchar有什么區(qū)別呢?

使用char時(shí)規(guī)定即使這個(gè)字段實(shí)際只存儲(chǔ)了1個(gè)字節(jié)但也會(huì)占X個(gè)字節(jié),這個(gè)X就是我們?cè)赾har(X)指定的長(zhǎng)度,例如:char(10) charset=utf8但是我們只存儲(chǔ)了一個(gè) ‘a’ ,實(shí)際上’a’只占用一個(gè)字節(jié),但是使用char就意味著最少需要占10個(gè)字節(jié),即使我們實(shí)際只存儲(chǔ)了一個(gè)字節(jié)

記錄頭信息

再來簡(jiǎn)單了解以下記錄頭信息, 我們先記住 record_type、next_record

這里再來講一下MySql如何刪除數(shù)據(jù)的,當(dāng)我們執(zhí)行一條 delete from table where id = 1 時(shí),該條記錄并不會(huì)被真正的刪除,而是打上一個(gè)標(biāo)記,表示這表記錄已經(jīng)刪除了,有人可能會(huì)有疑惑,那這樣不就會(huì)浪費(fèi)很多存儲(chǔ)空間嗎?不是的,實(shí)際上被刪除的數(shù)據(jù)會(huì)組成一個(gè)鏈表,當(dāng)我們插入數(shù)據(jù)時(shí)就會(huì)去這個(gè)鏈表上找已經(jīng)刪除的數(shù)據(jù),然后將已經(jīng)刪除的數(shù)據(jù)覆蓋,這樣做的好處就是可以提高刪除性能,不至于我們每次刪除數(shù)據(jù)的時(shí)候都要去移動(dòng)這些數(shù)據(jù),為什么會(huì)移動(dòng)數(shù)據(jù)?就像我們使用ArrayList一樣當(dāng)我們刪除一條記錄之后,后面的記錄要向前移動(dòng),而只打一個(gè)刪除標(biāo)記,后面我們?cè)谥赜眠@個(gè)空間就能避免數(shù)據(jù)移動(dòng)

索引頁(數(shù)據(jù)頁)

MySql的數(shù)據(jù)都是存儲(chǔ)在磁盤中的,當(dāng)我們需要查詢數(shù)據(jù)時(shí)它就會(huì)去磁盤加載數(shù)據(jù),這里就會(huì)有一個(gè)問題,我們一次性加載多少數(shù)據(jù)?一條一條的加載嗎?

這樣的話就太慢了,所以MySql將存儲(chǔ)空間分一個(gè)很多個(gè)頁,每個(gè)頁默認(rèn)是占用16KB,這個(gè)頁就是MySql每次加載數(shù)據(jù)的基本單位。

頁又分為很多中類型,比如:index頁、undo頁、XDES頁、INODE頁等

我們的每一條記錄都是存儲(chǔ)在一個(gè)類型為 index頁的上面,這個(gè)頁就是我們稱的索引頁(外面都是叫數(shù)據(jù)頁,數(shù)據(jù)頁即是索引頁)

先來看下索引頁的結(jié)構(gòu)

名稱描述占用大小
File Header文件頭部,用來表示一些頁的通用信息38字節(jié)
Page Header頁面頭部,表示數(shù)據(jù)頁的專有信息56字節(jié)
User Record用戶記錄,存儲(chǔ)我們插入的數(shù)據(jù)不確定
Free Space空閑空間不確定
Page Directory頁目錄不確定
File Trailer文件尾,校驗(yàn)頁是否完整8字節(jié)

這整個(gè)頁就是占用 16KB

我們重點(diǎn)只關(guān)注 UserRecord部分,因?yàn)槲覀兊挠脩粲涗浘褪谴鎯?chǔ)在這個(gè)部分。

record1和record2就是我們的用戶記錄,且這每條記錄都是按照我們上面的行格式存儲(chǔ)的,每條記錄都會(huì)組成一個(gè)鏈表,那這個(gè)鏈表是如何形成的呢?

還記得我們上面的行格式中,有個(gè)記錄頭,里面有個(gè)next_record字段,這個(gè)字段就指向下一條記錄,通過這個(gè)字段每條記錄之間就組成了一條鏈表,可以看到除了我們自己插入的兩條記錄外還有兩個(gè)分別叫 Infimum和Supremum的東西,其實(shí)這也是兩條記錄,只不過不是我們手動(dòng)插入的記錄,而是MySql為什么自動(dòng)生成記錄,所以也叫虛擬記錄,Infimum記錄表示最小的記錄,而Supremum表示最大的記錄,這是規(guī)定,規(guī)定這兩條記錄就是分別代表最小的記錄和最大的記錄,在這個(gè)數(shù)據(jù)頁中沒有記錄會(huì)比Infimum記錄小,也沒有記錄比Supremum記錄大,每個(gè)頁中的記錄都是有序的,會(huì)根據(jù)主鍵進(jìn)行排序,當(dāng)我們沒有顯示指定主鍵時(shí),并且表沒有不允許為NULL并且建立了唯一索引的字段時(shí),MySql就會(huì)為我們生成一個(gè)row_id的字段作為主鍵。

Infimum記錄指向的下一條記錄就是這個(gè)頁中用戶記錄(我們自己插入的記錄)的最小記錄,而指向Supremum記錄的用戶記錄就是這個(gè)頁中的最大記錄。

剛開始時(shí)這個(gè)頁就只有Infimum記錄和Supremum記錄,當(dāng)我們執(zhí)行insert語句向這個(gè)頁插入數(shù)據(jù)的時(shí)候就會(huì)向FreeSpace申請(qǐng)空間,然后就存儲(chǔ)這條記錄,直到這個(gè)數(shù)據(jù)頁中沒有可用空間,就會(huì)申請(qǐng)新的索引頁來保存數(shù)據(jù)

現(xiàn)在假設(shè)一個(gè)數(shù)據(jù)頁中存儲(chǔ)了7條記錄,當(dāng)我們要查找數(shù)據(jù)時(shí)如何查找?

當(dāng)我們要查詢 record5時(shí)難道從頭開始遍歷這個(gè)頁中的所有數(shù)據(jù)進(jìn)行比對(duì)嗎?

每個(gè)數(shù)據(jù)頁都有一個(gè)叫頁目錄的東西,可以想象以下我們平時(shí)看書時(shí),當(dāng)我們要查找某個(gè)內(nèi)容時(shí)都會(huì)先看目錄,然后快速定位到這個(gè)內(nèi)容所在的頁,索引頁中的頁目錄也是如此。

它把記錄分成多個(gè)組,當(dāng)這個(gè)數(shù)據(jù)頁中即使沒有一條用戶記錄時(shí)也會(huì)存在兩個(gè)組,即Infimum和Supremum組,那每個(gè)組的的記錄數(shù)又是多少呢?

對(duì)于Infimum所在的組只能有Infimum這一條記錄,而對(duì)于Supremum所在的組可以存儲(chǔ)1~8條數(shù)據(jù),而其他組則存儲(chǔ)4~8條數(shù)據(jù)。

然后把每個(gè)分組中的最大記錄地址存儲(chǔ)到頁目錄中,而頁目錄中的每個(gè)元素都稱為槽

當(dāng)我們只有7條用戶記錄時(shí),這個(gè)頁中分為兩個(gè)組,一個(gè)是Infimum記錄所在的組,一個(gè)是Supremum所在的組,而Supremum所在的組是可以存儲(chǔ)1~8條記錄的,所以這里剛好兩個(gè)組就能存儲(chǔ)下。

當(dāng)我們?cè)俨迦胍粭l記錄時(shí),就會(huì)再分裂出一個(gè)組,一個(gè)組存儲(chǔ)4條記錄,一個(gè)組存儲(chǔ)5條記錄

當(dāng)我們需要查找某個(gè)數(shù)據(jù)時(shí)就直接遍歷槽就可以了,就避免了遍歷頁中的所有數(shù)據(jù)

多個(gè)索引頁中如何查找數(shù)據(jù)

上面介紹的是在一個(gè)索引頁中如何查找數(shù)據(jù),但是在實(shí)際中我們的數(shù)據(jù)會(huì)占用很多個(gè)索引頁,那如何在多個(gè)索引頁中查找數(shù)據(jù)?

我們先做一個(gè)大膽的假設(shè),假設(shè)每個(gè)頁只能存儲(chǔ)3條記錄,并且先暫時(shí)去掉Infimum記錄和Supremum記錄。

如果每個(gè)頁只能存儲(chǔ)三條記錄的話,此時(shí)我們有9條記錄,那就需要占用3個(gè)數(shù)據(jù)頁,并且數(shù)據(jù)頁之間組成了一個(gè)雙向鏈表,像下面這樣

如果我們需要查找 p8這條數(shù)據(jù)如何查找?難道遍歷每個(gè)頁嗎?

然后在頁中又遍歷每個(gè)槽,如果我們的數(shù)據(jù)很多很多,需要很多索引頁,那這樣的查找速度就太慢了,很顯然MySql不可能這么干。

我們想一想我們?cè)谝粋€(gè)頁中是如何查找數(shù)據(jù)的?我們會(huì)把記錄分為一組一組的,然后額外記錄每個(gè)組的最大記錄,我們把記錄每個(gè)組最大記錄的地方稱為槽,然后我們?cè)诓檎覕?shù)據(jù)時(shí)就只需要遍歷槽就可以了。

在多個(gè)數(shù)據(jù)頁中查找多個(gè)記錄也是類似的,我們可以把每個(gè)頁存儲(chǔ)的最小記錄單獨(dú)記錄起來

再新開一個(gè)索引頁,記錄每個(gè)頁中的最小主鍵值還有起所在的頁號(hào),假設(shè)我們需要查找 p3時(shí)我們就直接到頁10中查找,發(fā)現(xiàn) p1 < p3 < p4從而定位到 頁20,然后在頁中遍歷每個(gè)槽就能很容易的找到某條記錄

心得

  • 每條記錄是如何存儲(chǔ)的
  • 每條記錄都會(huì)有我們的真實(shí)數(shù)據(jù)和一些額外數(shù)據(jù)
  • 數(shù)據(jù)在索引頁中會(huì)組成一個(gè)鏈表
  • 刪除數(shù)據(jù)時(shí)只是打了一個(gè)刪除標(biāo)記,并不是真正的數(shù)據(jù)
  • 刪除的數(shù)據(jù)也會(huì)組成一個(gè)鏈表,稱為垃圾鏈表
  • 每個(gè)索引頁中會(huì)生成兩條虛擬記錄分別是Infimum和Supremum記錄
  • 會(huì)將頁中的記錄分為很多個(gè)組,每個(gè)組的最大記錄會(huì)記錄到一個(gè)稱為槽的地方
  • 當(dāng)在單個(gè)索引頁查找數(shù)據(jù)時(shí)會(huì)遍歷每個(gè)槽來查找數(shù)據(jù)
  • 在有多個(gè)索引頁時(shí)會(huì)記錄每個(gè)頁的最小記錄以及該記錄所在的頁號(hào)

總結(jié)

以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。

相關(guān)文章

最新評(píng)論