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

Mysql行格式索引頁詳解

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

本篇文章以InnoDB存儲引擎為例,主要講兩個大知識點。

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

行格式

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

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

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

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

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

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

變長字段長度列表

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

那這個變長字段長度列表有什么用呢?

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

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

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

  • 情況一:當實際占用的字節(jié)小于等于127字節(jié)時使用一個字節(jié)記錄這個長度
  • 情況二:當實際占用的字節(jié)大于127時使用兩個字節(jié)記錄這個長度

NULL值列表

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

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

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

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

一條記錄到底是如何存儲的?

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

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)

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

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

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

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

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

char的特殊情況

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

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

使用char時規(guī)定即使這個字段實際只存儲了1個字節(jié)但也會占X個字節(jié),這個X就是我們在char(X)指定的長度,例如:char(10) charset=utf8但是我們只存儲了一個 ‘a’ ,實際上’a’只占用一個字節(jié),但是使用char就意味著最少需要占10個字節(jié),即使我們實際只存儲了一個字節(jié)

記錄頭信息

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

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

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

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

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

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

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

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

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

這整個頁就是占用 16KB

我們重點只關(guān)注 UserRecord部分,因為我們的用戶記錄就是存儲在這個部分。

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

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

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

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

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

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

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

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

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

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

當我們只有7條用戶記錄時,這個頁中分為兩個組,一個是Infimum記錄所在的組,一個是Supremum所在的組,而Supremum所在的組是可以存儲1~8條記錄的,所以這里剛好兩個組就能存儲下。

當我們再插入一條記錄時,就會再分裂出一個組,一個組存儲4條記錄,一個組存儲5條記錄

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

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

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

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

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

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

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

我們想一想我們在一個頁中是如何查找數(shù)據(jù)的?我們會把記錄分為一組一組的,然后額外記錄每個組的最大記錄,我們把記錄每個組最大記錄的地方稱為槽,然后我們在查找數(shù)據(jù)時就只需要遍歷槽就可以了。

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

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

心得

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

總結(jié)

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

相關(guān)文章

最新評論