InnoDB儲存引擎和大多數資料庫一樣,記錄是以行的形式儲存的。也就是說頁中儲存著表中一行行的資料。在InnoDB 1.0.x版本之前,InnoDB儲存引擎
提供了Compact和Redundant兩種格式來存允許存取記錄資料。
Redundant格式是為了相容之前版本而保留的,如果閱讀過InnoDB的原始碼,使用者會發現原始碼中是用PHYSICAL RECORD(NEW STYLE)和PHYSICAL RECORD(OLD STYLE)來區分兩種格式。
在MySql 5.1版本中,預設設定為Compact行格式。使用者可以通過下面命令查看錶使用的行格式,其中row_format屬性工作表示當前所使用的行記錄結構類型。
mysql> show table status like 'test'\G*************************** 1. row *************************** Name: test Engine: InnoDB Version: 10 Row_format: Compact Rows: 1 Avg_row_length: 16384 Data_length: 16384Max_data_length: 0 Index_length: 0 Data_free: 5242880 Auto_increment: NULL Create_time: 2016-07-20 16:13:43 Update_time: NULL Check_time: NULL Collation: utf8_general_ci Checksum: NULL Create_options: Comment: 1 row in set (0.00 sec)
可以看到test表是Compact的行格式。資料庫執行個體的作用之一就是讀取頁中存放的行記錄。如果使用者自己知道頁中行記錄的組織規則,也可以自行通過編寫工具的方式來讀取其中的記錄。 1、Compact行記錄格式
Compact行記錄是在MySql 5.0中引入的,其設計目標是高效地儲存資料。簡單來說,一個頁中存放的行資料越多,其效能就越高。下面是Compact行記錄的儲存方式:
Compact行記錄格式的首部是一個非NULL變長欄位長度列表,並且其是按照列的順序逆序放置的,其長度是:
(1)若列的長度小於255位元組,用1位元組表示;
(2)若大於255個位元組,用2位元組表示;
變長欄位的長度最大不可以超過2位元組,這是因為在MySql資料中varchar類型的最大長度限制為65535。
變長欄位之後的第二部分是NULL標誌位,該位指示了該行資料中是否有NULL值,有則用1表示。該部分所佔的位元組應該為1位元組。
接下來的部分是記錄頭資訊(record header),固定佔用5位元組(40位),每位的含義如下圖所示:
最後的部分是實際儲存每個列的資料。NULL不佔該部分任何空間,即NULL除了佔有NULL標誌位,實際儲存不佔有任何空間。
另外,每行資料除了使用者定義的列外,還有兩個隱藏列,事務ID列和復原指標列,分別為6位元組和7位元組的大小。若InnoDB表沒有定義主鍵,每行還會增加一個6位元組的rowid列。
接下來用一個具體樣本來分析Compact行記錄的內部結構:
mysql> create table mytest( -> t1 varchar(10), -> t2 varchar(10), -> t3 char(10), -> t4 varchar(10) -> )engine=innodb charset=latin1 row_format=compact;Query OK, 0 rows affected (0.09 sec)
下面插入資料:
mysql> insert into mytest values('a','bb','bb','ccc'); Query OK, 1 row affected (0.02 sec)mysql> insert into mytest values('d', 'ee', 'ee', 'fff');Query OK, 1 row affected (0.01 sec)mysql> insert into mytest values('d', NULL, NULL, 'fff');Query OK, 1 row affected (0.01 sec)
查詢表中的資料:
mysql> select * from mytest\G*************************** 1. row ***************************t1: at2: bbt3: bbt4: ccc*************************** 2. row ***************************t1: dt2: eet3: eet4: fff*************************** 3. row ***************************t1: dt2: NULLt3: NULLt4: fff3 rows in set (0.00 sec)
如果啟用了參數innodb_file_per_table,則開啟資料表空間檔案mytest.ibd。否則,開啟預設的共用資料表空間檔案ibdata1,本文中沒有啟用參數innodb_file_per_table,所有開啟檔案ibdata1.
在Linux下,首先找到ibdata1檔案的位置。
root@TryHard:~# find / -name ibdata1;/var/lib/mysql/ibdata1
然後,將二進位檔案ibdata1重新導向為mytest.txt檔案,然後開啟mytest.txt檔案。
root@TryHard:~# hexdump -C -v /var/lib/mysql/ibdata1 > mytest.txt
找到如下圖所示的內容:
下面是對第一行內容進行分析:
03 02 01 是變長欄位長度列表,t1,t2,t3欄位是變長欄位,逆序顯示這三個欄位的長度。插入的資料中t3的值為3個位元組,t2的值為2個位元組,t1的值為1個位元組。
00 是NULL標誌位,為0表示第一行沒有NULL值。
00 00 10 00 2c 是Record Header,固定5位元組長度。
00 00 00 00 09 00 是InnoDB自動建立的rowID,6位元組長度。
兩個隱藏列,事務ID列和復原指標列,分別為6位元組和7位元組的大小。
00 00 00 00 1e 09 是事務ID列,6位元組長度。
8a 00 00 01 3d 01 10 是復原指標列,7位元組長度。
61是t1列的資料’a’;
62 62是t2列的資料’bb’;
62 62 20 20 20 20 20 20 20 是t3列的資料’bb’,t2是固定長度char欄位,如果未用完其佔用的長度空間時,會用0x20來進行填充。
63 63 63是t4列的資料’ccc’;
Record Header的值是00 00 10 00 2c,最後兩個位元組是00 2c,這兩個位元組代表next_recorder,0x2c代表下一個記錄的位移量,即目前記錄的位置加上位移量0x2c就是下一條記錄的起始位置。所以InnoDB儲存引擎在頁內部是通過一種鏈表的結構來串聯各個行記錄的。
第二行和第一行類似 。
下面是第三行的內容:
03 01 變長欄位長度列表,逆序;t1長度為1,t4長度為3,t2和t3為NULL。
06 NULL標誌位,第三行有NULL值。
00 00 20 ff 98 是Record Header;
00 00 00 00 09 02是RowId,6個位元組;
00 00 00 00 1e 0b是事務ID列,6個位元組;
8c 00 00 02 1d 01 10是復原指標列,7個位元組。
64是t1欄位的值’d’;
66 66 66是t4欄位的值’f’;
第三行有NULL值,因為NULL標誌位不再是00而是06,轉換成二進位00000110,為1的值表示第2列和第3列的資料為NULL。其後儲存列資料的部分,沒有儲存NULL列,而只儲存了第1列和第4列的非NULL的值,這也說明了,不管是char還是varchar類型,在compact格式下NULL值都不佔用任何儲存空間。 2、Redundant行記錄格式
Redundant是MySql 5.0版本之前InnoDB的行記錄儲存方式,MySql 5.0支援Redundant是為了相容之前版本的頁格式。Redundant行記錄採用下圖所示的方式儲存。
Redundant行記錄格式的首部是一個欄位長度位移列表,同樣是安裝列的順序逆序存放的。若列的長度小於255位元組,用1位元組表示;若大於255位元組,用2位元組表示。第二個部分為記錄頭資訊(record header),不同於Compact行記錄格式,Redundant行記錄格式的記錄頭佔用6位元組,每位的含義如下圖所示:
其中,n_fields的值代表一行中列的數量,佔用10位,所以能表示的最大值為1023,這也解釋了為什麼MySql資料庫一行支援的最多的列數量為1023。另一個需要注意的值為1byte_offs_flag,該值定義了位移列表佔用1位元組還是2位元組。
最後部分就是實際儲存的每個列的資料了。
建立一個Redundant表mytest2:
mysql> create table mytest2 engine=innodb row_format=redundant as select * from mytest;Query OK, 3 rows affected (0.04 sec)Records: 3 Duplicates: 0 Warnings: 0
查看錶mytest2資訊:
mysql> show table status like 'mytest2'\G*************************** 1. row *************************** Name: mytest2 Engine: InnoDB Version: 10 Row_format: Redundant Rows: 3 Avg_row_length: 5461 Data_length: 16384Max_data_length: 0 Index_length: 0 Data_free: 5242880 Auto_increment: NULL Create_time: 2017-03-20 15:46:57 Update_time: NULL Check_time: NULL Collation: utf8_general_ci Checksum: NULL Create_options: row_format=REDUNDANT Comment: 1 row in set (0.00 sec)
查看資料:
mysql> select * from mytest2\G *************************** 1. row ***************************t1: at2: bbt3: bbt4: ccc*************************** 2. row ***************************t1: dt2: eet3: eet4: fff*************************** 3. row ***************************t1: dt2: NULLt3: NULLt4: fff3 rows in set (0.00 sec)
將二進位檔案ibdata1重新導向為mytest.txt檔案,然後開啟mytest.txt檔案。
root@TryHard:~# hexdump -C -v /var/lib/mysql/ibdata1 > mytest.txt
第一行資料:
23 20 16 14 13 0c 06 是長度位移列表,逆序。
00 00 10 0f 00 ba 是Record Header,固定6個位元組;
00 00 00 00 09 0c 是rowId列,6個位元組。
00 00 00 00 1e 1b 是事務ID列,6個位元組。
9a 00 00 01 4b 01 10 是復原指標列,7個位元組。
61 是t1列資料’a’;
62 62是t2列資料’bb’;
62 62 20 20 20 20 20 20 20 20 是t3列資料’bb’, char類型;
63 63 63是t4列資料’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).
第1列指的是rowID列,第2列指的是事務ID列,第3列指的是復原指標列,第4列指的是t1,……
在接下來的記錄頭資訊(record Header)中應該注意到48位中的第22-32位,為0000000111,表示表共有7個列(包含了隱藏的3列),接下來的第33位為1,大表位移列表為一個位元組。
後面的資訊就是實際存放的資料。
第三行資料:
21 9e 94 14 13 0c 06 長度位移列表,逆序;
00 00 20 0f 00 74 是Record Header,固定長度為6位元組;
00 00 00 00 09 0e是RowId列;
00 00 00 00 1e 1b 是事務id,6個位元組;
9a 00 00 01 4b 01 2e 是復原指標列,7 個位元組;
64是列t1的資料’d’;
t2類型是varchar,並且值為NULL,所以t2的值不佔用儲存空間。
00 00 00 00 00 00 00 00 00 00 是t3的資料NULL,t3是char類型;
66 66 66是列t4的資料’fff’;
長度位移列表逆序之後得到 06, 0c, 13, 14, 94, 9e, 21,前4個值都好理解。
第5個NULL值變為了94(該列是t2欄位),第6個char類型的NULL值為9e(94+10=0x9e)(該列是t3的欄位),之後的21大表(14+3 = 0x21)。可以看出對於varchar類型的NULL值,Redundant行記錄格式同樣不佔用任何儲存空間,而char類型的NULL值需要佔用空間。
當前表mytest2的字元集為Latin1,每個字元最多隻佔用1個位元組。若使用者將表mytest2的字元集轉換為utf8,第三列char固定長度類型不再是只佔用10位元組了,而是10*3 = 30位元組。