InnoDB記錄結(jié)構(gòu)
存儲引擎的作用就是內(nèi)存和磁盤打交道,負(fù)責(zé)將數(shù)據(jù)在他兩之間傳輸,為了提高傳輸效率,提出了“頁”的概念,也就是在一般情況下,為了減少io次數(shù),一次最少從磁盤中讀取16KB的內(nèi)容到內(nèi)存中,一次最少把內(nèi)存中的16KB內(nèi)容刷新到磁盤中。
頁:將數(shù)據(jù)劃分為若干個(gè)頁,以頁作為磁盤和內(nèi)存之間交互的基本單位,InnoDB中頁的大小一般為 16 KB。
#?innoDB行格式
行格式:記錄在磁盤上的存放方式稱為行格式。
四種行格式:Compact 、 Redundant 、Dynamic 和 Compressed 行格式。
#?指定行格式命令
#?介紹InnoDB的Compact行格式
可以把記錄分為記錄額外信息,記錄真實(shí)數(shù)據(jù)

#?記錄額外信息
邊長字段長度列表:記錄可變長度字段的真實(shí)數(shù)據(jù)占用的字節(jié)數(shù)
注意:變長字段長度列表中只存儲值為 非NULL 的列內(nèi)容占用的長度,值為 NULL 的列的長度是不儲存的
null值列表:值為 NULL 的列統(tǒng)一管理起來,存儲到 NULL 值列表中
記錄頭信息:固定的 5 個(gè)字節(jié)組成,存放記錄的描述信息


#?真實(shí)數(shù)據(jù)
除了存放真實(shí)數(shù)據(jù)外,還會添加隱藏列,具體如下:
列名 是否必須 占用空間 描述 row_id 否 6 字節(jié) 行ID,唯一標(biāo)識一條記錄 transaction_id 是 6 字節(jié) 事務(wù)ID roll_pointer 是 7 字節(jié) 回滾指針
注意:實(shí)際上這幾個(gè)列的真正名稱其實(shí)是:DB_ROW_ID、DB_TRX_ID、DB_ROLL_PTR,我們?yōu)榱嗣烙^才寫成了row_id、transaction_id和roll_pointer。
#?char(M)的存儲格式
對于 CHAR(M) 類型的列來說,當(dāng)列采用的是定長字符集時(shí),該列占用的字節(jié)數(shù)不會被加到變長字段長度列表,而如果采用變長字符集時(shí),該列占用的字節(jié)數(shù)也會被加到變長字段長度列表
#?表的行格式采用compact實(shí)驗(yàn)
目前表結(jié)構(gòu)
windows下 到C:\ProgramData\MySQL\MySQL Server 5.7\Data\test
下找到文件record_test_1.ibd
,使用notepad++ 16進(jìn)制打開(在notepad++找到插件->HEX-editor-> view in HEX,如果沒有就安裝一個(gè))

我們先來看第一條記錄的content
,extra
字段,字段長度分別為8和16,字符類型都是可變的,所以這兩個(gè)字段是會被存放在變長字段長度列表
,
又因?yàn)榇娣艜r(shí)的順序是逆序的,所以應(yīng)該是 ?10 08

#?Redundant行格式
沒太理解,暫不記錄
##行溢出數(shù)據(jù)
#?VARCHAR(M)最多能存儲的數(shù)據(jù)
存儲一個(gè) VARCHAR(M) 類型的列,其實(shí)需要占用3部分存儲空間:
真實(shí)數(shù)據(jù)
真實(shí)數(shù)據(jù)占用字節(jié)的長度
NULL 值標(biāo)識,如果該列有 NOT NULL 屬性則可以沒有這部分存儲空間
首先前提是VARCHAR(M) 類型的列最多可以占用 65535 個(gè)字節(jié)!
現(xiàn)在假設(shè)列的字符集是ascii 字符集(一個(gè)字符代表一個(gè)字節(jié)),表只有一個(gè)列
如果列有
not null
則不需要花一個(gè)字節(jié)空間存null值,真實(shí)數(shù)據(jù)的長度可能占用2個(gè)字節(jié),即可以最大有65533個(gè)字節(jié)來存儲真實(shí)數(shù)據(jù),則可以存65533個(gè)字符如果列沒有
not null
則需要花一個(gè)字節(jié)空間存null值,且真實(shí)數(shù)據(jù)的長度可能占用2個(gè)字節(jié),即可以最大有65532個(gè)字節(jié)來存儲真實(shí)數(shù)據(jù),則可以存65532個(gè)字符
但是,如果字符集不是ascii 字符集呢?是utf8呢?gbk呢?
其實(shí)前面算的最大字節(jié)數(shù)是不用變得,因?yàn)樽址母淖?,一個(gè)字符最大占用字節(jié)數(shù)量變了。
比如字符集是utf8的話,一個(gè)字符最大使用3個(gè)字節(jié),所以上面的②情況,最大存儲字符數(shù)65532/3=21844
上述所言在列的值允許為NULL的情況下,gbk字符集下M的最大取值就是32766,utf8字符集下M的最大取值就是21844,這都是在表中只有一個(gè)字段的情況下說的,一定要記住一個(gè)行中的所有列(不包括隱藏列和記錄頭信息)占用的字節(jié)長度加起來不能超過65535個(gè)字節(jié)!
#?記錄中的數(shù)據(jù)太多產(chǎn)生的溢出
在 Compact 和 Reduntant 行格式中,對于占用存儲空間非常大的列,在 記錄的真實(shí)數(shù)據(jù) 處只會存儲該列的一部分?jǐn)?shù)據(jù),把剩余的數(shù)據(jù)分散存儲在幾個(gè)其他的頁中,然后 記錄的真實(shí)數(shù)據(jù) 處用20個(gè)字節(jié)存儲指向這些頁的地址(當(dāng)然這20個(gè)字節(jié)中還包括這些分散在其他頁面中的數(shù)據(jù)的占用的字節(jié)數(shù)),從而可以找到剩余數(shù)據(jù)所在的頁, 如圖所示:

最后需要注意的是,不只是 VARCHAR(M) 類型的列,其他的 TEXT、BLOB 類型的列在存儲數(shù)據(jù)非常多的時(shí)候也會發(fā)生?行溢出
#?行溢出的臨界點(diǎn)
假設(shè)一個(gè)列中存儲的數(shù)據(jù)字節(jié)數(shù)為n,那么發(fā)生 行溢出 現(xiàn)象時(shí)需要滿足這個(gè)式子:
注釋:
136
:每個(gè)頁除了存放我們的記錄以外,也需要存儲一些額外的信息,亂七八糟的額外信息加起來需要 136 個(gè)字節(jié)的空間(現(xiàn)在只要知道這個(gè)數(shù)字就好了),其他的空間都可以被用來存儲記錄。
2
:一個(gè)頁中至少存放兩行記錄。
27
:每個(gè)記錄需要的額外信息是 27 字節(jié)。
16384
: 一頁16kb = 16384byte
但這只是假設(shè)一個(gè)表只有一個(gè)列的情況,有多個(gè)列的話計(jì)算公式和結(jié)果都要修改
結(jié)論:你不用關(guān)注這個(gè)臨界點(diǎn)是什么,只要知道如果我們想一個(gè)行中存儲了很大的數(shù)據(jù)時(shí),可能發(fā)生 行溢出 的現(xiàn)象。
#?Dynamic和Compressed行格式
#?Dynamic格式
MySQL 版本是 5.7 ,它的默認(rèn)行格式就是 Dynamic ,這倆行格式和 Compact 行格式挺像,只不過在處理 行溢出 數(shù)據(jù)時(shí)有點(diǎn)兒分歧,它們不會在記 錄的真實(shí)數(shù)據(jù)處存儲字段真實(shí)數(shù)據(jù)的前 768 個(gè)字節(jié),而是把所有的字節(jié)都存儲到其他頁面中,只在記錄的真實(shí)數(shù)據(jù)處存儲其他頁面的地址,就像這樣:

#?Compressed行格式
Compressed 行格式和 Dynamic 不同的一點(diǎn)是, Compressed 行格式會采用壓縮算法對頁面進(jìn)行壓縮,以節(jié)省空間。