InnoDB存儲引擎和大多數數據庫同樣(如Oracle和Microsoft SQL Server數據庫),記錄是以行的形式存儲的。這意味着頁中保存着表中一行行的數據。到MySQL 5.1時,InnoDB存儲引擎提供了Compact和Redundant兩種格式來存放行記錄數據,Redundant是爲兼容以前版本而保留的,若是你閱讀過InnoDB的源代碼,會發現源代碼中是用PHYSICAL RECORD(NEW STYLE)和PHYSICAL RECORD(OLD STYLE)來區分兩種格式的。MySQL 5.1默認保存爲Compact行格式。你能夠經過命令SHOW TABLE STATUS LIKE 'table_name'來查看當前表使用的行格式,其中row_format就表明了當前使用的行記錄結構類型。例如:mysql
use mysql;算法
show table status like 'user'\Gsql
數據庫實例的一個做用就是讀取頁中存放的行記錄。若是咱們知道規則,那麼也能夠讀取其中的記錄,如以前的py_innodb_page_info工具。下面將具體分析各格式存放數據的規則。數據庫
Compact行記錄是在MySQL 5.0時被引入的,其設計目標是能高效存放數據。簡單來講,若是一個頁中存放的行數據越多,其性能就越高。Compact行記錄以以下方式進行存儲: c#
Compact行格式的首部是一個非NULL變長字段長度列表,並且是按照列的順序逆序放置的。當列的長度小於255字節,用1字節表示,若大於255個字節,用2個字節表示,變長字段的長度最大不能夠超過2個字節(這也很好地解釋了爲何MySQL中varchar的最大長度爲65 535,由於2個字節爲16位,即216=1=65 535)。第二個部分是NULL標誌位,該位指示了該行數據中是否有NULL值,用1表示。該部分所佔的字節應該爲bytes。接下去的部分是爲記錄頭信息(record header),固定佔用5個字節(40位),每位的含義見下表4-1。最後的部分就是實際存儲的每一個列的數據了,須要特別注意的是,NULL不佔該部分任何數據,即NULL除了佔有NULL標誌位,實際存儲不佔有任何空間。另外有一點須要注意的是,每行數據除了用戶定義的列外,還有兩個隱藏列,事務ID列和回滾指針列,分別爲6個字節和7個字節的大小。若InnoDB表沒有定義Primary Key,每行還會增長一個6字節的RowID列。工具
下面用一個具體事例來分析Compact行記錄的內部結構:性能
create table mytest (spa
t1 varchar(10),設計
t2 varchar(10),指針
t3 char(10),
t4 varchar(10)
) engine=innodb charset=latin1 row_format=compact;
insert into mytest values('a','bb','bb','ccc');
insert into mytest values('d','ee','ee','fff');
insert into mytest values('d',NULL,NULL,'fff');
select * from mytest\G;
建立了mytest表,有4個列,t一、t二、t4都爲varchar變長字段類型,t3爲固定長度類型char。接着咱們插入了3條有表明性的數據,接着打開mytest.ibd(啓用了innodb_file_per_table,若你沒有啓用該選項,請打開默認的共享表空間文件ibdata1)。在Windows下,能夠選擇用UltraEdit打開該二進制文件(在Linux環境下,使用hexdump -C -v mytest.ibd>mytest.txt便可),打開mytest.txt文件,找到以下內容:
0000c070 73 75 70 72 65 6d 75 6d 03 02 01 00 00 00 10 00|supremum…… 0000c080 2c 00 00 00 2b 68 00 00 00 00 00 06 05 80 00 00|,……+h…… 0000c090 00 32 01 10 61 62 62 62 62 20 20 20 20 20 20 20|.2..abbbb 0000c0a0 20 63 63 63 03 02 01 00 00 00 18 00 2b 00 00 00|ccc……+…… 0000c0b0 2b 68 01 00 00 00 00 06 06 80 00 00 00 32 01 10|+h……2.. 0000c0c0 64 65 65 65 65 20 20 20 20 20 20 20 20 66 66 66|deeeefff 0000c0d0 03 01 06 00 00 20 ff 98 00 00 00 2b 68 02 00 00|……+h…… 0000c0e0 00 00 06 07 80 00 00 00 32 01 10 64 66 66 66 00|……2..dfff. 該行記錄從0000c078開始,若整理以下,相信你會有更好的理解: 03 02 01/*變長字段長度列表,逆序*/ 00/*NULL標誌位,第一行沒有NULL值*/ 00 00 10 00 2c/*記錄頭信息,固定5字節長度*/ 00 00 00 2b 68 00/*RowID咱們建的表沒有主鍵,所以會有RowID*/ 00 00 00 00 06 05/*TransactionID*/ 80 00 00 00 32 01 10/*Roll Pointer*/ 61/*列1數據'a'*/ 62 62/*列2'bb'*/ 62 62 20 20 20 20 20 20 20 20/*列3數據'bb'*/ 63 63 63/*列4數據'ccc'*/
如今第一行數據就展示在咱們眼前了。須要注意的是,變長字段長度列表是逆序存放的,03 02 01,而不是01 02 03。還須要注意的是InnoDB每行有隱藏列。同時能夠看到,固定長度char字段在未填充滿其長度時,會用0x20來進行填充。再來分析一下,記錄頭信息的最後4個字節表明next_recorder,0x6800表明下一個記錄的偏移量,當前記錄的位置+0x6800就是下一條記錄的起始位置。因此InnoDB存儲引擎在頁內部是經過一種鏈表的結構來串聯各個行記錄的。
第二行我將不作整理,除了RowID不一樣外,它和第一行大同小異,有興趣的讀者能夠用上面的方法本身試試。
如今咱們關注有NULL值的第三行:
03 01/*變長字段長度列表,逆序*/
06/*NULL標誌位,第三行有NULL值*/
00 00 20 ff 98/*記錄頭信息*/
00 00 00 2b 68 02/*RowID*/
00 00 00 00 06 07/*TransactionID*/
80 00 00 00 32 01 10/*Roll Pointer*/
64/*列1數據'd'*/
66 66 66/*列4數據'fff'*/
第三行有NULL值,所以NULL標誌位再也不是00而是06了,轉換成二進制爲00000110,爲1的值即表明了第2列和第3列的數據爲NULL,在其後存儲列數據的部分,咱們會發現沒有存儲NULL,只存儲了第1列和第4列非NULL的值。這個例子很好地說明了:不論是char仍是varchar類型,NULL值是不佔用存儲空間的。
Redundant是MySQL 5.0版本以前InnoDB的行記錄存儲方式,MySQL 5.0支持Redundant是爲了向前兼容性。Redundant行記錄以以下方式存儲:
從上圖能夠看到,不一樣於Compact行記錄格式,Redundant行格式的首部是一個字段長度偏移列表,一樣是按照列的順序逆序放置的。當列的長度小於255字節,用1字節表示;若大於255個字節,用2個字節表示。第二個部分爲記錄頭信息(record header),不一樣於Compact行格式,Redundant行格式固定佔用6個字節(48位),每位的含義見表4-2。從表中能夠看到,n_fields值表明一行中列的數量,佔用10位,這也很好地解釋了爲何MySQL一個行支持最多的列爲1023。另外一個須要注意的值爲1byte_offs_flags,該值定義了偏移列表佔用1個字節仍是2個字節。最後的部分就是實際存儲的每一個列的數據了。
建立一張和mytest內容徹底同樣、但行格式爲Redundant的表mytest2
create table mytest2 engine=innodb row_format=redundant as (select * from mytest);
show table status like 'mytest2'\G
select * from mytest2\G
如今row_format變爲Redundant。一樣,經過hexdump將表空間mytest2.ibd導出到文本文件mytest2.txt。打開文件,找到相似以下行:
0000c070 08 03 00 00 73 75 70 72 65 6d 75 6d 00 23 20 16|……supremum.#. 0000c080 14 13 0c 06 00 00 10 0f 00 ba 00 00 00 2b 68 0b|……+h. 0000c090 00 00 00 00 06 53 80 00 00 00 32 01 10 61 62 62|……S……2..abb 0000c0a0 62 62 20 20 20 20 20 20 20 20 63 63 63 23 20 16|bb ccc#. 0000c0b0 14 13 0c 06 00 00 18 0f 00 ea 00 00 00 2b 68 0c|……+h. 0000c0c0 00 00 00 00 06 53 80 00 00 00 32 01 1e 64 65 65|……S……2..dee 0000c0d0 65 65 20 20 20 20 20 20 20 20 66 66 66 21 9e 94|ee fff!.. 0000c0e0 14 13 0c 06 00 00 20 0f 00 74 00 00 00 2b 68 0d|……t……+h. 0000c0f0 00 00 00 00 06 53 80 00 00 00 32 01 2c 64 00 00|……S……2.,d.. 0000c100 00 00 00 00 00 00 00 00 66 66 66 00 00 00 00 00|……fff…… 整理能夠獲得以下內容: 23 20 16 14 13 0c 06/*長度偏移列表,逆序*/ 00 00 10 0f 00 ba/*記錄頭信息,固定6個字節*/ 00 00 00 2b 68 0b/*RowID*/ 00 00 00 00 06 53/*TransactionID*/ 80 00 00 00 32 01 10/*Roll Point*/ 61/*列1數據'a'*/ 62 62/*列2數據'bb'*/ 62 62 20 20 20 20 20 20 20 20/*列3數據'bb'Char類型*/ 63 63 63/*列4數據'ccc'*/
23 20 16 14 13 0c 06,逆轉爲06,0c,13,14,16,20,23。分別表明第一列長度6,第二列長度6(6+6=0x0C),第三列長度爲7(6+6+7=0x13),第四列長度1(6+6+7+1=0x14),第五列長度2(6+6+7+1+2=0x16),第六列長度10(6+6+7+1+2+10=0x20),第七列長度3(6+6+7+1+2+10+3=0x23)。
記錄頭信息中應該注意48位中22~32位,爲0000000111,表示表共有7個列(包含了隱藏的3列),接下去的33位爲1,表明偏移列表爲一個字節。
後面的信息就是實際每行存放的數據了,這與Compact行格式大體相同。
請注意是大體相同,由於若是咱們來看第三行,會發現對於NULL的處理二者是不一樣的。
21 9e 94 14 13 0c 06/*長度偏移列表,逆序*/
00 00 20 0f 00 74/*記錄頭信息,固定6個字節*/
00 00 00 2b 68 0d/*RowID*/
00 00 00 00 06 53/*TransactionID*/
80 00 00 00 32 01 10/*Roll Point*/
64/*列1數據'a'*/
00 00 00 00 00 00 00 00 00 00/*列3數據NULL*/
66 66 66/*列4數據'fff'*/
這裏與以前Compact行格式有着很大的不一樣了,首先來看長度偏移列表,咱們逆序排列後獲得06 0c 13 14 94 9e 21,前4個值都很好理解,第5個NULL變爲了94,接着第6個列char類型的NULL值爲9e(94+10=0x9e),以後的21表明14+3=0x21。能夠看到對於varchar的NULL值,Redundant行格式一樣不佔用任何存儲空間,於是char類型的NULL值須要佔用空間。
當前表mytest2的字符集爲Latin1,每一個字符最多隻佔用1個字節。若這裏將表mytest2的字符集轉換爲utf8,第三列char固定長度類型就再也不是隻佔用10個字節了,而是10×3=30個字節,Redundant行格式下char固定字符類型將會佔據可能存放的最大值字節數。
InnoDB Plugin引入了新的文件格式(file format,能夠理解爲新的頁格式),對於之前支持的Compact和Redundant格式將其稱爲Antelope文件格式,新的文件格式稱爲Barracuda。Barracuda文件格式下擁有兩種新的行記錄格式Compressed和Dynamic兩種。新的兩種格式對於存放BLOB的數據採用了徹底的行溢出的方式,在數據頁中只存放20個字節的指針,實際的數據都存放在BLOB Page中,而以前的Compact和Redundant兩種格式會存放768個前綴字節。
下圖是Barracuda文件格式的溢出行:
Compressed行記錄格式的另外一個功能就是,存儲在其中的行數據會以zlib的算法進行壓縮,所以對於BLOB、TEXT、VARCHAR這類大長度類型的數據能進行很是有效的存儲。