首頁  >  文章  >  資料庫  >  MySQL如何從二進位內容看InnoDB行格式

MySQL如何從二進位內容看InnoDB行格式

王林
王林轉載
2023-06-03 09:55:281177瀏覽

MySQL如何從二進位內容看InnoDB行格式

InnoDB是一種儲存引擎,它可以將表中的資料儲存到磁碟上,因此在重新啟動後即使伺服器已關機,我們的資料仍然可以保留。而真正處理資料的過程是發生在記憶體中的,所以需要把磁碟中的資料載入到記憶體中,如果是處理寫入或修改請求的話,還需要把記憶體中的內容刷新到磁碟上。而我們知道讀寫磁碟的速度非常慢,和記憶體讀寫差了幾個數量級,所以當我們想從表中獲取某些記錄時,InnoDB儲存引擎需要一條一條的把記錄從磁碟上讀出來麼?

InnoDB採取的方式是:將資料分割為若干個頁,以頁為磁碟和記憶體之間互動的基本單位,InnoDB中頁的大小一般為16KB。也就是在一般情況下,一次最少從磁碟中讀取16KB的內容到記憶體中,一次最少把記憶體中的16KB內容刷新到磁碟中。

mysql> show variables like '%innodb_page_size%';
+------------------+-------+
| Variable_name    | Value |
+------------------+-------+
| innodb_page_size | 16384 |
+------------------+-------+
1 row in set (0.00 sec)

我們通常會以記錄為單位將資料插入表中,這些記錄儲存在磁碟上的方式也稱為行格式或記錄格式。 InnoDB儲存引擎設計了4種不同類型的行格式,分別是Compact、Redundant、Dynamic和Compressed行格式。

行記錄格式的分類和介紹

在早期的InnoDB版本中,由於檔案格式只有一種,因此不需要為此檔案格式命名。為了支援新功能,針對早期版本不相容的情況,InnoDB引擎已經開發了新的檔案格式。為了在升級和降級情況下幫助管理系統的兼容性,以及運行不同的MySQL版本,InnoDB開始使用命名的檔案格式。

MySQL如何從二進位內容看InnoDB行格式

在msyql 5.7.9及以後版本,預設行格式由innodb_default_row_format變數決定,它的預設值是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)

查看目前表使用的行格式:

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=行格式名称ALTER TABLE 表名 ROW_FORMAT=行格式名称;

#如果要修改現有表的行模式為compressed或dynamic,必須先將檔案格式設定成Barracuda: set global innodb_file_format=Barracuda;,再用ALTER TABLE tablename ROW_FORMAT=COMPRESSED;去修改才能生效。

行格式

COMPACT

MySQL如何從二進位內容看InnoDB行格式

#變長欄位清單

MySQL支援一些變長的數據類型,例如VARCHAR(M)、VARBINARY(M)、各種TEXT類型,各種BLOB類型,我們也可以把擁有這些資料類型的列稱為變長字段,變長字段中儲存多少位元組的資料是不固定的,所以我們在儲存真實資料的時候需要順便把這些資料佔用的位元組數也存起來。如果該可變欄位允許儲存的最大位元組數(M×W)超過255位元組且真實儲存的位元組數(L)超過127位元組,則使用2個位元組記錄,否則使用1個位元組記錄。

問題一:那為什麼要用128當分界線呢?一個位元組可以最多表示255,但是MySQL設計長度表示時,為了區分是否是一個位元組表示長度,規定,如果最高位元為1,那麼就是兩個位元組表示長度,否則就是一個位元組。例如,01111111,這個就代表長度為127,而如果長度是128,就需要兩個字節,就是10000000 10000000,首個字節的最高位為1,那麼這就是兩個字節表示長度的開頭,第二個位元組可以用所有位元表示長度,需要注意的是,MySQL採取Little Endian的計數方式,低位在前,高位在後,所以129就是10000001 10000000。這種標識方法的最大長度為32767,即32KB。

問題二:如果兩個位元組也不夠表示的長度,該怎麼辦? innoDB頁大小預設為16KB,對於一些佔用字節數非常多的字段,比方說某個字段長度大於了16KB,那麼如果該記錄在單個頁面中無法存儲時,InnoDB會把一部分資料存放到所謂的溢出頁中,在變長欄位長度清單處只儲存留在本頁面的長度,所以使用兩個位元組也可以存放下來。這個溢出頁機制參考後面的資料溢出。

NULL值清單

表中的某些欄位可能儲存NULL值,如果把這些NULL值都放到記錄的真實資料中儲存會很佔地方,所以Compact行格式把這些值為NULL的欄位統一管理起來,儲存到NULL值清單。對於每個可以儲存NULL的資料列,都會有一個對應的二進位位,當該二進位位的值為1時,表示該列的值為NULL。二進位位元的值為0時,代表該列的值不為NULL。

記錄頭資訊

用於描述記錄的記錄頭訊息,它是由固定的5個位元組組成。 5個位元組也就是40個二進位位,不同的位元代表不同的意思。

字段 长度(bit) 说明
预留位1 1 没有使用
预留位2 1 没有使用
delete_mask 1 标记该记录是否被删除
min_rec_mask 1 B+树的每层非叶子节点中的最小记录都会添加该标记
n_owned 4 表示当前记录拥有的记录数
heap_no 13 表示当前记录在页的位置信息
record_type 3 表示当前记录的类型,0 表示普通记录,1 表示B+树非叶子节点记录,2 表示最小记录,3 表示最大记录
next_record 16 表示下一条记录的相对位置

隐藏列

记录的真实数据除了我们自己定义的列的数据以外,MySQL会为每个记录默认的添加一些列(也称为隐藏列),包括:

  • DB_ROW_ID(row_id):非必须,6字节,表示行ID,唯一标识一条记录

  • DB_TRX_ID:必须,6字节,表示事务ID

  • DB_ROLL_PTR:必须,7字节,表示回滚指针

InnoDB表对主键的生成策略是:优先使用用户自定义主键作为主键,如果用户没有定义主键,则选取一个Unique键作为主键,如果表中连Unique 键都没有定义的话,则InnoDB会为表默认添加一个名为row_id的隐藏列作为主键。

DB_TRX_ID(也可以称为trx_id) 和DB_ROLL_PTR(也可以称为roll_ptr) 这两个列是必有的,但是row_id是可选的(在没有自定义主键以及Unique 键的情况下才会添加该列)。

其他的行格式和Compact行格式差别不大。

Redundant行格式

Redundant行格式是MySQL5.0之前用的一种行格式,不予深究。

Dynamic行格式

MySQL5.7的默认行格式就是Dynamic,Dynamic行格式和Compact行格式挺像,只不过在处理行溢出数据时有所不同。

Compressed行格式

Compressed行格式在Dynamic行格式的基础上会采用压缩算法对页面进行压缩,以节省空间。以zlib的算法进行压缩,因此对于BLOB、TEXT、VARCHAR这类大长度数据能够进行有效的存储(减少40%,但对CPU要求更高)。

数据溢出

如果我们定义一个表,表中只有一个VARCHAR字段,如下:

CREATE TABLE test_varchar( c VARCHAR(60000))

然后往这个字段插入60000个字符,会发生什么?前边说过,MySQL中磁盘和内存交互的基本单位是页,也就是说MySQL是以页为基本单位来管理存储空间的,我们的记录都会被分配到某个页中存储。而一个页的大小一般是16KB,也就是16384字节,而一个VARCHAR(M)类型的列就最多可以存储65532个字节,这样就可能造成一个页存放不了一条记录的情况。

在Compact和Redundant行格式中,对于占用存储空间非常大的列,在记录的真实数据处只会存储该列的该列的前768个字节的数据,然后把剩余的数据分散存储在几个其他的页中,记录的真实数据处用20个字节(768字节后20个字节)存储指向这些页的地址。这个过程也叫做行溢出,存储超出768字节的那些页面也被称为溢出页。

Dynamic和Compressed行格式,不会在记录的真实数据处存储字段真实数据的前768个字节,而是把所有的字节都存储到其他页面中,只在记录的真实数据处存储其他页面的地址。

实战分析行格式

准备表及数据:

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环境下,使用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 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开始,第一行整理如下:

03 02 01 // 变长字段长度列表,逆序,t4列长度为3,t2列长度为2,t1列长度为1
00 // NULL标志位,第一行没有NULL值
00 00 10 00 2c // 记录头信息,固定5字节长度
00 00 00 2b 68 00 // RowID我们建的表没有主键,因此会有RowID,固定6字节长度
00 00 00 00 06 05 // 事务ID,固定6个字节80 00 00 00 32 01 10 // 回滚指针,固定7个字节61 
// t1数据'a'62 62 
// t2'bb'62 62 20 20 20 20 20 20 20 20 // t3数据'bb'63 63 63 // t4数据'ccc'

第二行整理如下:

03 02 01 // 变长字段长度列表,逆序,t4列长度为3,t2列长度为2,t1列长度为1
00 // NULL标志位,第二行没有NULL值
00 00 18 00 2b // 记录头信息,固定5字节长度
00 00 00 00 02 01 // RowID我们建的表没有主键,因此会有RowID,固定6字节长度
00 00 00 00 0f 62 // 事务ID,固定6个字节
c9 00 00 01 b2 01 10 // 回滚指针,固定7个字节64 // t1数据'd'65 65 
// t2数据'ee'65 65 20 20 20 20 20 20 20 20 // t3数据'ee'66 66 66 
// t4数据'fff'

第三行整理如下:

03 01 // 变长字段长度列表,逆序,t4列长度为3,t1列长度为1
06 // 00000110 NULL标志位,t2和t3列为空
00 00 20 ff 98  // 记录头信息,固定5字节长度
00 00 00 00 02 02 // RowID我们建的表没有主键,因此会有RowID,固定6字节长度
00 00 00 00 0f 67 // 事务ID,固定6个字节
cc 00 00 01 b6 01 10 // 回滚指针,固定7个字节64 // t1数据'd'66 66 66 // t4数据'fff'

接下来更新下数据:

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)

查看二进制内容(需要等一会,有可能只写入了缓存,磁盘上的文件并没有更新):

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开始,第一行整理如下:

03 01 // 变长字段长度列表,逆序,t4列长度为3,t1列长度为1
02 // 0000 0010 NULL标志位,表示t2为null
00 00 10 00 58 // 记录头信息,固定5字节长度
00 00 00 00 02 00 // RowID我们建的表没有主键,因此会有RowID,固定6字节长度
00 00 00 00 0f 68 // 事务ID,固定6个字节
4d 00 00 01 9e 04 a9 // 回滚指针,固定7个字节61 // t1数据'a'62 62 20 20 20 20 20 20 20 20 // t3数据'bb'63 63 63 // t4数据'ccc'

第二行整理如下:

03 02 01 // 变长字段长度列表,逆序,t4列长度为3,t2列长度为2,t1列长度为1
00 // NULL标志位,第二行没有NULL值20 00 18 00 00 // 0010 delete_mask=1 标记该记录是否被删除  记录头信息,固定5字节长度
00 00 00 00 02 01 // RowID我们建的表没有主键,因此会有RowID,固定6字节长度
00 00 00 00 0f 6a // 事务ID,固定6个字节
4e 00 00 01 9f 10 c0 // 回滚指针,固定7个字节64 // t1数据'd'65 65 // t2数据'ee'65 65 20 20 20 20 20 20 20 20 // t3数据'ee'66 66 66 // t4数据'fff'

第三行数据未发生变化。

以上是MySQL如何從二進位內容看InnoDB行格式的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文轉載於:yisu.com。如有侵權,請聯絡admin@php.cn刪除