1. InnoDB、行格式
InnoDB是Mysql默認(rèn)的存儲(chǔ)引擎,由于磁盤本身的讀寫速度很慢,InnoDB將數(shù)據(jù)劃分為若干個(gè)頁,以頁作為磁盤和內(nèi)存之間交互的基本單位,InnoDB中頁的大小一般為 16 KB。也就是說,通常情況下,InnoDB一次最少從磁盤中讀取16KB數(shù)據(jù)到內(nèi)存中,一次最少從內(nèi)存中寫16KB數(shù)據(jù)到磁盤上。
在InnoDB中,數(shù)據(jù)存儲(chǔ)在硬盤上的格式被稱為“行格式”或“記錄格式”,InnoDB擁有四種不同的“行格式”。
- COMPACT
直接看圖:

什么是變長(zhǎng)字段長(zhǎng)度列表?
一些數(shù)據(jù)類型可以用來指定變長(zhǎng)字符(即表示一個(gè)字符需要的字節(jié)數(shù)不確定,比如在utf8字符集中),比如varchar、text等,我們需要將該數(shù)據(jù)真實(shí)占用的字節(jié)長(zhǎng)度記錄下來(注意,變長(zhǎng)字段長(zhǎng)度列表存儲(chǔ)的是字節(jié)長(zhǎng)度,而不是字節(jié)本身)并將各變長(zhǎng)字段長(zhǎng)度以逆序排列。
什么是NULL值列表?
將值為NULL的列統(tǒng)一管理起來,存儲(chǔ)到NULL值列表中。如何記錄呢?將每個(gè)可以為NULL值的列對(duì)應(yīng)一個(gè)二進(jìn)制位,若該位置為1,則代表相應(yīng)的列值為NULL。
NULL值列表必須為整數(shù)個(gè)字節(jié),若不足一個(gè)字節(jié),則在高位補(bǔ)0。比如一張表只有三個(gè)字段,那么NULL值列表也必須要有一個(gè)字節(jié)(8個(gè)二進(jìn)制位),其中前5位為0,后3位記錄字段的值是否為NULL。
什么是記錄頭信息?
記錄頭信息包含了一些當(dāng)前記錄的信息,占用5個(gè)字節(jié)。具體有哪些信息?如下:
delete_mask:標(biāo)記是否已經(jīng)被刪除。是的,即使一條數(shù)據(jù)被刪除,這里很可能只是邏輯刪除,它在物理上也可以依然可以留存在頁中。這些被刪除的記錄之所以不立即從磁盤上移除,是因?yàn)橐瞥鼈冎蟀哑渌挠涗浽诖疟P上重新排列需要性能消耗,所以只是打一個(gè)刪除標(biāo)記而已,所有被刪除掉的記錄都會(huì)組成一個(gè)所謂的垃圾鏈表,在這個(gè)鏈表中的記錄占用的空間稱之為所謂的可重用空間,之后如果有新記錄插入到表中的話,可能把這些被刪除的記錄占用的存儲(chǔ)空間覆蓋掉。
min_rec_mask:B+樹的每層非葉子節(jié)點(diǎn)中的最小記錄都會(huì)添加該標(biāo)記。
n_owned:
heap_no:表示當(dāng)前記錄在頁中的位置。
record_type:表示當(dāng)前記錄的類型。0表示普通記錄,1表示B+樹非葉節(jié)點(diǎn)記錄,2表示最小記錄,3表示最大記錄。
next_record:表示從當(dāng)前記錄的真實(shí)數(shù)據(jù)到下一條記錄的真實(shí)數(shù)據(jù)的地址偏移量。(注意這里指向的是真實(shí)數(shù)據(jù)的開頭,不是完整數(shù)據(jù),完整數(shù)據(jù)包含真實(shí)數(shù)據(jù)和額外信息)
隱藏列?
除了我們存儲(chǔ)的數(shù)據(jù)外,Mysql還會(huì)默認(rèn)添加一些列,被稱為隱藏列,隱藏列包含:行ID、事務(wù)ID、回滾指針。
有同學(xué)可能有疑問?每行數(shù)據(jù)不是有自定義的主鍵作為行ID嗎?為什么隱藏列還要生成?InnoDB的主鍵策略是:優(yōu)先使用自定義主鍵,若未自定義,則選用一個(gè)Unique鍵作為主鍵,若Unique鍵也未設(shè)置,這才使用隱藏列中的行ID。
其余三種行格式有:Redundant、Dynamic、Compressed。不再一一細(xì)看了,需要用到時(shí)可以再針對(duì)性查資料。
2. 頁
上面提到了“頁”這個(gè)概念,它是管理內(nèi)存和磁盤之間數(shù)據(jù)的基本單位。它的組成部分如圖所示:

每插入一條記錄,InnoDB將會(huì)從Free Space申請(qǐng)一塊空間交付給User Records使用,存儲(chǔ)我們的數(shù)據(jù)。如果Free Space用完,那么將要申請(qǐng)一塊新的頁。
3. 頁目錄
每條記錄行在User Records中是以鏈表形式,以主鍵為順序排列的,那如果我要查找其中主鍵為n的一條記錄呢?
最笨的辦法就是遍歷,但這種方法一旦面對(duì)大量的數(shù)據(jù)無疑效率低下。為了解決這個(gè)問題,InnoDB設(shè)計(jì)了頁目錄。具體是如何設(shè)計(jì)的呢?如下:
首先,將所有的記錄分成若干個(gè)組;每個(gè)組的最后一條記錄的頭信息中的n_owned屬性表示該組擁有多少條記錄;將每個(gè)組最后一條記錄的地址偏移量(主要目的是為了計(jì)算個(gè)數(shù))提取出來,這些偏移量被稱為“槽”(slot),存儲(chǔ)在頁的尾部,這個(gè)地方就是頁目錄(Page Diretory)。
從邏輯上簡(jiǎn)單來說,就是將所有記錄分組,然后將每組的偏移量與個(gè)數(shù)存入頁目錄,每組的記錄個(gè)數(shù)一般在1-8之間。
所以在插入數(shù)據(jù)時(shí),首先通過主鍵找到合適的槽,該槽對(duì)應(yīng)的n_owned值加一,一旦大于8,則分裂為兩個(gè)槽。
如果要查找主鍵為n的記錄,先通過二分法找到相應(yīng)的槽,然后再遍歷這個(gè)槽。