本篇文章給大家?guī)?lái)了關(guān)于mysql的相關(guān)知識(shí),其中主要介紹了關(guān)于從二進(jìn)制內(nèi)容看InnoDB行格式的相關(guān)問(wèn)題,InnoDB是一個(gè)將表中的數(shù)據(jù)存儲(chǔ)到磁盤(pán)上的存儲(chǔ)引擎,所以即使關(guān)機(jī)后重啟我們的數(shù)據(jù)還是存在的,希望對(duì)大家有幫助。
推薦學(xué)習(xí):mysql視頻教程
InnoDB是一個(gè)將表中的數(shù)據(jù)存儲(chǔ)到磁盤(pán)上的存儲(chǔ)引擎,所以即使關(guān)機(jī)后重啟我們的數(shù)據(jù)還是存在的。而真正處理數(shù)據(jù)的過(guò)程是發(fā)生在內(nèi)存中的,所以需要把磁盤(pán)中的數(shù)據(jù)加載到內(nèi)存中,如果是處理寫(xiě)入或修改請(qǐng)求的話,還需要把內(nèi)存中的內(nèi)容刷新到磁盤(pán)上。而我們知道讀寫(xiě)磁盤(pán)的速度非常慢,和內(nèi)存讀寫(xiě)差了幾個(gè)數(shù)量級(jí),所以當(dāng)我們想從表中獲取某些記錄時(shí),InnoDB存儲(chǔ)引擎需要一條一條的把記錄從磁盤(pán)上讀出來(lái)么?
InnoDB采取的方式是:將數(shù)據(jù)劃分為若干個(gè)頁(yè),以頁(yè)作為磁盤(pán)和內(nèi)存之間交互的基本單位,InnoDB中頁(yè)的大小一般為16KB。也就是在一般情況下,一次最少?gòu)拇疟P(pán)中讀取16KB的內(nèi)容到內(nèi)存中,一次最少把內(nèi)存中的16KB內(nèi)容刷新到磁盤(pán)中。
mysql> show variables like '%innodb_page_size%'; +------------------+-------+ | Variable_name | Value | +------------------+-------+ | innodb_page_size | 16384 | +------------------+-------+ 1 row in set (0.00 sec)
我們平時(shí)是以記錄為單位來(lái)向表中插入數(shù)據(jù)的,這些記錄在磁盤(pán)上的存放方式也被稱(chēng)為行格式或者記錄格式。InnoDB存儲(chǔ)引擎設(shè)計(jì)了4種不同類(lèi)型的行格式,分別是Compact、Redundant、Dynamic和Compressed行格式。
行記錄格式的分類(lèi)和介紹
在早期的InnoDB版本中,由于文件格式只有一種,因此不需要為此文件格式命名。隨著InnoDB引擎的發(fā)展,開(kāi)發(fā)出了不兼容早期版本的新文件格式,用于支持新的功能。為了在升級(jí)和降級(jí)情況下幫助管理系統(tǒng)的兼容性,以及運(yùn)行不同的MySQL版本,InnoDB開(kāi)始使用命名的文件格式。
在msyql 5.7.9及以后版本,默認(rèn)行格式由innodb_default_row_format變量決定,它的默認(rèn)值是dynamic:
mysql> show variables like "innodb_file_format"; +--------------------+-----------+ | Variable_name | Value | +--------------------+-----------+ | innodb_file_format | Barracuda | +--------------------+-----------+ 1 row in set (0.01 sec) mysql> show variables like "innodb_default_row_format"; +---------------------------+---------+ | Variable_name | Value | +---------------------------+---------+ | innodb_default_row_format | dynamic | +---------------------------+---------+ 1 row in set (0.00 sec)
查看當(dāng)前表使用的行格式:
mysql> show table status like 'dept_emp'G*************************** 1. row *************************** Name: dept_emp Engine: InnoDB Version: 10 Row_format: Dynamic Rows: 331570 Avg_row_length: 36 Data_length: 12075008Max_data_length: 0 Index_length: 5783552 Data_free: 0 Auto_increment: NULL Create_time: 2021-08-11 09:04:36 Update_time: NULL Check_time: NULL Collation: latin1_swedish_ci Checksum: NULL Create_options: Comment:1 row in set (0.00 sec)
指定表的行格式:
CREATE TABLE 表名(列的信息) ROW_FORMAT=行格式名稱(chēng)ALTER TABLE 表名 ROW_FORMAT=行格式名稱(chēng);
如果要修改現(xiàn)有表的行模式為compressed或dynamic,必須先將文件格式設(shè)置成Barracuda:set global innodb_file_format=Barracuda;,再用ALTER TABLE tablename ROW_FORMAT=COMPRESSED;去修改才能生效。
行格式
COMPACT
變長(zhǎng)字段列表
MySQL支持一些變長(zhǎng)的數(shù)據(jù)類(lèi)型,比如VARCHAR(M)、VARBINARY(M)、各種TEXT類(lèi)型,各種BLOB類(lèi)型,我們也可以把擁有這些數(shù)據(jù)類(lèi)型的列稱(chēng)為變長(zhǎng)字段,變長(zhǎng)字段中存儲(chǔ)多少字節(jié)的數(shù)據(jù)是不固定的,所以我們?cè)诖鎯?chǔ)真實(shí)數(shù)據(jù)的時(shí)候需要順便把這些數(shù)據(jù)占用的字節(jié)數(shù)也存起來(lái)。如果該可變字段允許存儲(chǔ)的最大字節(jié)數(shù)(M×W)超過(guò)255字節(jié)并且真實(shí)存儲(chǔ)的字節(jié)數(shù)(L)超過(guò)127字節(jié),則使用2個(gè)字節(jié)記錄,否則使用1個(gè)字節(jié)記錄。
問(wèn)題一:那么為什么用128作為分界線呢? 一個(gè)字節(jié)可以最多表示255,但是MySQL設(shè)計(jì)長(zhǎng)度表示時(shí),為了區(qū)分是否是一個(gè)字節(jié)表示長(zhǎng)度,規(guī)定,如果最高位為1,那么就是兩個(gè)字節(jié)表示長(zhǎng)度,否則就是一個(gè)字節(jié)。例如,01111111,這個(gè)就代表長(zhǎng)度為127,而如果長(zhǎng)度是128,就需要兩個(gè)字節(jié),就是10000000 10000000,首個(gè)字節(jié)的最高位為1,那么這就是兩個(gè)字節(jié)表示長(zhǎng)度的開(kāi)頭,第二個(gè)字節(jié)可以用所有位表示長(zhǎng)度,并且需要注意的是,MySQL采取Little Endian的計(jì)數(shù)方式,低位在前,高位在后,所以129就是10000001 10000000。同時(shí),這種標(biāo)識(shí)方式,最大長(zhǎng)度就是 2^15-1=32767,也就是32KB。
問(wèn)題二:如果兩個(gè)字節(jié)也不夠表示的長(zhǎng)度,該怎么辦? innoDB頁(yè)大小默認(rèn)為16KB,對(duì)于一些占用字節(jié)數(shù)非常多的字段,比方說(shuō)某個(gè)字段長(zhǎng)度大于了16KB,那么如果該記錄在單個(gè)頁(yè)面中無(wú)法存儲(chǔ)時(shí),InnoDB會(huì)把一部分?jǐn)?shù)據(jù)存放到所謂的溢出頁(yè)中,在變長(zhǎng)字段長(zhǎng)度列表處只存儲(chǔ)留在本頁(yè)面中的長(zhǎng)度,所以使用兩個(gè)字節(jié)也可以存放下來(lái)。這個(gè)溢出頁(yè)機(jī)制參考后面的數(shù)據(jù)溢出。
NULL值列表
表中的某些列可能存儲(chǔ)NULL值,如果把這些NULL值都放到記錄的真實(shí)數(shù)據(jù)中存儲(chǔ)會(huì)很占地方,所以Compact行格式把這些值為NULL的列統(tǒng)一管理起來(lái),存儲(chǔ)到NULL值列表。每個(gè)允許存儲(chǔ)NULL的列對(duì)應(yīng)一個(gè)二進(jìn)制位,二進(jìn)制位的值為1時(shí),代表該列的值為NULL。二進(jìn)制位的值為0時(shí),代表該列的值不為NULL。
記錄頭信息
用于描述記錄的記錄頭信息,它是由固定的5個(gè)字節(jié)組成。5個(gè)字節(jié)也就是40個(gè)二進(jìn)制位,不同的位代表不同的意思。
字段 | 長(zhǎng)度(bit) | 說(shuō)明 |
---|---|---|
預(yù)留位1 | 1 | 沒(méi)有使用 |
預(yù)留位2 | 1 | 沒(méi)有使用 |
delete_mask | 1 | 標(biāo)記該記錄是否被刪除 |
min_rec_mask | 1 | B+樹(shù)的每層非葉子節(jié)點(diǎn)中的最小記錄都會(huì)添加該標(biāo)記 |
n_owned | 4 | 表示當(dāng)前記錄擁有的記錄數(shù) |
heap_no | 13 | 表示當(dāng)前記錄在頁(yè)的位置信息 |
record_type | 3 | 表示當(dāng)前記錄的類(lèi)型,0 表示普通記錄,1 表示B+樹(shù)非葉子節(jié)點(diǎn)記錄,2 表示最小記錄,3 表示最大記錄 |
next_record | 16 | 表示下一條記錄的相對(duì)位置 |
隱藏列
記錄的真實(shí)數(shù)據(jù)除了我們自己定義的列的數(shù)據(jù)以外,MySQL會(huì)為每個(gè)記錄默認(rèn)的添加一些列(也稱(chēng)為隱藏列),包括:
-
DB_ROW_ID(row_id):非必須,6字節(jié),表示行ID,唯一標(biāo)識(shí)一條記錄
-
DB_TRX_ID:必須,6字節(jié),表示事務(wù)ID
-
DB_ROLL_PTR:必須,7字節(jié),表示回滾指針
InnoDB表對(duì)主鍵的生成策略是:優(yōu)先使用用戶(hù)自定義主鍵作為主鍵,如果用戶(hù)沒(méi)有定義主鍵,則選取一個(gè)Unique鍵作為主鍵,如果表中連Unique 鍵都沒(méi)有定義的話,則InnoDB會(huì)為表默認(rèn)添加一個(gè)名為row_id的隱藏列作為主鍵。
DB_TRX_ID(也可以稱(chēng)為trx_id) 和DB_ROLL_PTR(也可以稱(chēng)為roll_ptr) 這兩個(gè)列是必有的,但是row_id是可選的(在沒(méi)有自定義主鍵以及Unique 鍵的情況下才會(huì)添加該列)。
其他的行格式和Compact行格式差別不大。
Redundant行格式
Redundant行格式是MySQL5.0之前用的一種行格式,不予深究。
Dynamic行格式
MySQL5.7的默認(rèn)行格式就是Dynamic,Dynamic行格式和Compact行格式挺像,只不過(guò)在處理行溢出數(shù)據(jù)時(shí)有所不同。
Compressed行格式
Compressed行格式在Dynamic行格式的基礎(chǔ)上會(huì)采用壓縮算法對(duì)頁(yè)面進(jìn)行壓縮,以節(jié)省空間。以zlib的算法進(jìn)行壓縮,因此對(duì)于BLOB、TEXT、VARCHAR這類(lèi)大長(zhǎng)度數(shù)據(jù)能夠進(jìn)行有效的存儲(chǔ)(減少40%,但對(duì)CPU要求更高)。
數(shù)據(jù)溢出
如果我們定義一個(gè)表,表中只有一個(gè)VARCHAR字段,如下:
CREATE TABLE test_varchar( c VARCHAR(60000))
然后往這個(gè)字段插入60000個(gè)字符,會(huì)發(fā)生什么?前邊說(shuō)過(guò),MySQL中磁盤(pán)和內(nèi)存交互的基本單位是頁(yè),也就是說(shuō)MySQL是以頁(yè)為基本單位來(lái)管理存儲(chǔ)空間的,我們的記錄都會(huì)被分配到某個(gè)頁(yè)中存儲(chǔ)。而一個(gè)頁(yè)的大小一般是16KB,也就是16384字節(jié),而一個(gè)VARCHAR(M)類(lèi)型的列就最多可以存儲(chǔ)65532個(gè)字節(jié),這樣就可能造成一個(gè)頁(yè)存放不了一條記錄的情況。
在Compact和Redundant行格式中,對(duì)于占用存儲(chǔ)空間非常大的列,在記錄的真實(shí)數(shù)據(jù)處只會(huì)存儲(chǔ)該列的該列的前768個(gè)字節(jié)的數(shù)據(jù),然后把剩余的數(shù)據(jù)分散存儲(chǔ)在幾個(gè)其他的頁(yè)中,記錄的真實(shí)數(shù)據(jù)處用20個(gè)字節(jié)(768字節(jié)后20個(gè)字節(jié))存儲(chǔ)指向這些頁(yè)的地址。這個(gè)過(guò)程也叫做行溢出,存儲(chǔ)超出768字節(jié)的那些頁(yè)面也被稱(chēng)為溢出頁(yè)。
Dynamic和Compressed行格式,不會(huì)在記錄的真實(shí)數(shù)據(jù)處存儲(chǔ)字段真實(shí)數(shù)據(jù)的前768個(gè)字節(jié),而是把所有的字節(jié)都存儲(chǔ)到其他頁(yè)面中,只在記錄的真實(shí)數(shù)據(jù)處存儲(chǔ)其他頁(yè)面的地址。
實(shí)戰(zhàn)分析行格式
準(zhǔn)備表及數(shù)據(jù):
create table row_test ( t1 varchar(10), t2 varchar(10), t3 char(10), t4 varchar(10) ) engine=innodb charset=latin1 row_format=compact; insert into row_test values('a','bb','bb','ccc'); insert into row_test values('d','ee','ee','fff'); insert into row_test values('d',NULL,NULL,'fff');
在Linux環(huán)境下,使用hexdump -C -v mytest.ibd>mytest.txt,打開(kāi)mytest.txt文件,找到如下內(nèi)容:
0000c070 73 75 70 72 65 6d 75 6d 03 02 01 00 00 00 10 00 |supremum........| 0000c080 2c 00 00 00 00 02 00 00 00 00 00 0f 61 c8 00 00 |,...........a...| 0000c090 01 d4 01 10 61 62 62 62 62 20 20 20 20 20 20 20 |....abbbb | 0000c0a0 20 63 63 63 03 02 01 00 00 00 18 00 2b 00 00 00 | ccc........+...| 0000c0b0 00 02 01 00 00 00 00 0f 62 c9 00 00 01 b2 01 10 |........b.......| 0000c0c0 64 65 65 65 65 20 20 20 20 20 20 20 20 66 66 66 |deeee fff| 0000c0d0 03 01 06 00 00 20 ff 98 00 00 00 00 02 02 00 00 |..... ..........| 0000c0e0 00 00 0f 67 cc 00 00 01 b6 01 10 64 66 66 66 00 |...g.......dfff.|
該行記錄從0000c078開(kāi)始,第一行整理如下:
03 02 01 // 變長(zhǎng)字段長(zhǎng)度列表,逆序,t4列長(zhǎng)度為3,t2列長(zhǎng)度為2,t1列長(zhǎng)度為1 00 // NULL標(biāo)志位,第一行沒(méi)有NULL值 00 00 10 00 2c // 記錄頭信息,固定5字節(jié)長(zhǎng)度 00 00 00 2b 68 00 // RowID我們建的表沒(méi)有主鍵,因此會(huì)有RowID,固定6字節(jié)長(zhǎng)度 00 00 00 00 06 05 // 事務(wù)ID,固定6個(gè)字節(jié)80 00 00 00 32 01 10 // 回滾指針,固定7個(gè)字節(jié)61 // t1數(shù)據(jù)'a'62 62 // t2'bb'62 62 20 20 20 20 20 20 20 20 // t3數(shù)據(jù)'bb'63 63 63 // t4數(shù)據(jù)'ccc'
第二行整理如下:
03 02 01 // 變長(zhǎng)字段長(zhǎng)度列表,逆序,t4列長(zhǎng)度為3,t2列長(zhǎng)度為2,t1列長(zhǎng)度為1 00 // NULL標(biāo)志位,第二行沒(méi)有NULL值 00 00 18 00 2b // 記錄頭信息,固定5字節(jié)長(zhǎng)度 00 00 00 00 02 01 // RowID我們建的表沒(méi)有主鍵,因此會(huì)有RowID,固定6字節(jié)長(zhǎng)度 00 00 00 00 0f 62 // 事務(wù)ID,固定6個(gè)字節(jié) c9 00 00 01 b2 01 10 // 回滾指針,固定7個(gè)字節(jié)64 // t1數(shù)據(jù)'d'65 65 // t2數(shù)據(jù)'ee'65 65 20 20 20 20 20 20 20 20 // t3數(shù)據(jù)'ee'66 66 66 // t4數(shù)據(jù)'fff'
第三行整理如下:
03 01 // 變長(zhǎng)字段長(zhǎng)度列表,逆序,t4列長(zhǎng)度為3,t1列長(zhǎng)度為1 06 // 00000110 NULL標(biāo)志位,t2和t3列為空 00 00 20 ff 98 // 記錄頭信息,固定5字節(jié)長(zhǎng)度 00 00 00 00 02 02 // RowID我們建的表沒(méi)有主鍵,因此會(huì)有RowID,固定6字節(jié)長(zhǎng)度 00 00 00 00 0f 67 // 事務(wù)ID,固定6個(gè)字節(jié) cc 00 00 01 b6 01 10 // 回滾指針,固定7個(gè)字節(jié)64 // t1數(shù)據(jù)'d'66 66 66 // t4數(shù)據(jù)'fff'
接下來(lái)更新下數(shù)據(jù):
mysql> update row_test set t2=null where t1='a'; Query OK, 1 row affected (0.02 sec) Rows matched: 1 Changed: 1 Warnings: 0 mysql> delete from row_test where t2='ee'; Query OK, 1 row affected (0.01 sec)
查看二進(jìn)制內(nèi)容(需要等一會(huì),有可能只寫(xiě)入了緩存,磁盤(pán)上的文件并沒(méi)有更新):
0000c070 73 75 70 72 65 6d 75 6d 03 01 02 00 00 10 00 58 |supremum.......X| 0000c080 00 00 00 00 02 00 00 00 00 00 0f 68 4d 00 00 01 |...........hM...| 0000c090 9e 04 a9 61 62 62 20 20 20 20 20 20 20 20 63 63 |...abb cc| 0000c0a0 63 63 63 63 03 02 01 00 20 00 18 00 00 00 00 00 |cccc.... .......| 0000c0b0 00 02 01 00 00 00 00 0f 6a 4e 00 00 01 9f 10 c0 |........jN......| 0000c0c0 64 65 65 65 65 20 20 20 20 20 20 20 20 66 66 66 |deeee fff| 0000c0d0 03 01 06 00 00 20 ff 98 00 00 00 00 02 02 00 00 |..... ..........| 0000c0e0 00 00 0f 67 cc 00 00 01 b6 01 10 64 66 66 66 00 |...g.......dfff.|
該行記錄從0000c078開(kāi)始,第一行整理如下:
03 01 // 變長(zhǎng)字段長(zhǎng)度列表,逆序,t4列長(zhǎng)度為3,t1列長(zhǎng)度為1 02 // 0000 0010 NULL標(biāo)志位,表示t2為null 00 00 10 00 58 // 記錄頭信息,固定5字節(jié)長(zhǎng)度 00 00 00 00 02 00 // RowID我們建的表沒(méi)有主鍵,因此會(huì)有RowID,固定6字節(jié)長(zhǎng)度 00 00 00 00 0f 68 // 事務(wù)ID,固定6個(gè)字節(jié) 4d 00 00 01 9e 04 a9 // 回滾指針,固定7個(gè)字節(jié)61 // t1數(shù)據(jù)'a'62 62 20 20 20 20 20 20 20 20 // t3數(shù)據(jù)'bb'63 63 63 // t4數(shù)據(jù)'ccc'
第二行整理如下:
03 02 01 // 變長(zhǎng)字段長(zhǎng)度列表,逆序,t4列長(zhǎng)度為3,t2列長(zhǎng)度為2,t1列長(zhǎng)度為1 00 // NULL標(biāo)志位,第二行沒(méi)有NULL值20 00 18 00 00 // 0010 delete_mask=1 標(biāo)記該記錄是否被刪除 記錄頭信息,固定5字節(jié)長(zhǎng)度 00 00 00 00 02 01 // RowID我們建的表沒(méi)有主鍵,因此會(huì)有RowID,固定6字節(jié)長(zhǎng)度 00 00 00 00 0f 6a // 事務(wù)ID,固定6個(gè)字節(jié) 4e 00 00 01 9f 10 c0 // 回滾指針,固定7個(gè)字節(jié)64 // t1數(shù)據(jù)'d'65 65 // t2數(shù)據(jù)'ee'65 65 20 20 20 20 20 20 20 20 // t3數(shù)據(jù)'ee'66 66 66 // t4數(shù)據(jù)'fff'
第三行數(shù)據(jù)未發(fā)生變化。
推薦學(xué)習(xí):mysql視頻教程