MySQL服务端主要分为Server层和存储引擎层两部分,存储引擎层主要负责数据的存储与查询,常见的存储引擎有InnnoDB、MyISAM、Memory、RocksDB,在MySQL5.5.5开始InnoDB就开始作为默认存储引擎在MySQL中。

InnoDB将数据存储在磁盘上,数据处理时加载到内存,在Update或者Insert时还需要从内存写入磁盘,为了降低磁盘IO损耗,InnoDB将数据划分为多页,每次磁盘与内存间操作时都按页进行处理,InnoDB页大小通常为16Kb,也就是每次读取或写入磁盘时都会至少操作16Kb的内容。

InnoDB记录行结构

InnoDB包含四种行结构,RaedundantCompactDynamicCompressed

Compact行格式

Compack行格式结构如下:

image.png

CREATE TABLE record_format_demo (
	c1 VARCHAR(10),
	c2 VARCHAR(10) NOT NULL,
	c3 CHAR(10),
	c4 VARCHAR(10),
) CHARSET=ASCII ROW_FORMAT=COMPACT;

INSERT INTO record_format_demo (c1,c2,c3,c4)  VALUES('aaaa', 'bbb', 'cc', 'd'), ('eeee', 'fff', null, null);

假如现在有一张表,字符集采用ascii, 行格式采用Compact,表内包含4个列,c1,c2,c4是可变长度类型varchar,其中c2列不为null,c3是一个定长类型char。varchar(10)表示最多可存10个字符,因为ascii字符集一个字节就可以表示一个字符,所以这几个列最大占用字节数都是10。

两条记录用16进制表示如下。

未命名文件.png

一条完整的记录可以分为记录的额外信息和记录的真实数据两大部分。

  • 记录的额外信息

    • 变长字段列表
      存储变长数据类型的非NULL列所对应的长度,把所有变长字段的真实数据占用的长度都存放在记录的开头位置,从而形成一个变长字段长度列表,各变长字段数据占用的字节数按照列的顺序逆序存放。
      如果该可变字段允许存储的最大字节数超过255字节并且真实存储的字节数超过127字节,则使用2个字节,否则使用一个字节。

    对照第一条记录,c4列存储的数据是d,长度是1,用16进制表示为0x01,c2列存储的数据是bbb,长度是3,则表示为0x03,以此类推。

    • NULL值列表
      如果表中没有允许存储NULL的列,则NULL值列表也不存在了,否则将每个允许存储NULL的列对应一个二进制位,二进制位按照列的顺序逆序排列,二进制位表示的意义如下:

    二进制位的值为1时,表示该列的值为null。
    二进制位的值位0时,表示该列的值不为null。
    MySQL规定NULL值列表必须用整数个字节的位表示,如果使用的二进制位个数不是整数个字节,则在字节的高位补0。假如一个表只有3个值允许为NULL的列,不足8位,则在高位补0,用一个字节表示。假如有9个列,就用两个字节表示。

    • 记录头信息
      固定占用5个字节,具体含义如下:
      未命名文件 1.png
  • 记录的真实数据
    MySQL会为每个记录默认的添加一些列(隐藏列),如下

列名是否必须描述
row_id行ID,唯一标识一条记录
transcation_id事务ID
roll_pointer滚动指针

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

InnoDB存储引擎会为每条记录添加transcation_id和roll_pointer两个列,以为record_format_demo没有主键,所以添加了row_id。
1、 表record_format_demo使用的是ASCII字符集,所以0x61616161就表示aaaa,0x626262表示bbb,以此类推。
2、注意第1条记录中c3列的值是char(10)类型的,他存储的字符串是cc,而ASCII字符集16进制表示是0x6363,虽然这个字符串只占用了两个字节,但整个c3列缺占用了10个字节的空间,除真实数据外的8个字节都用空格字符填充,空格字符的ASCII字符集为0x20。

注意队医CHAR(M)类型的列来说,如果表使用变长字符集时,该列占用的字节数也会被加到变长字段长度列表。并且因为使用变长字符集,所以存储字节长度不一定,当占用字节数少的字符串变为字节数多的字符串时需要开辟新空间,会产生存储碎片。

Redundant行格式

Redundant是MySQL5.0之前的行结构,与Compact主要区别是字段偏移量长度、NULL值列表等。

image.png

  • 记录的额外信息
    • 字段偏移列表
      表示该行记录所有列(包括隐藏列)的长度信息都按照逆序存储,采用两个相邻数值的差值来计算各个列值的长度。
    • 记录头信息
      Redundant行格式的记录头信息占用6个字节,48个二进制位,这些二进制位代表的含义如下:
名称大小(单位:bit)描述
预留位11没有使用
预留位21没有使用
delete_mask1标记该记录是否被删除
min_rec_mask1B+树的每层非叶子节点中的最小纪录都会添加该记录
n_owned4表示当前记录拥有的记录数
heap_no13表示当前记录在页面堆的位置消息
n_field10表示记录中列的数量
1byte_offs_flag1标记字段长度偏移列表中每个列对应的偏移量是使用1字节表示还是2字节表示。
next_record16表示下一条记录的绝对位置
  • 1byte_offs_flag的取值逻辑

    • 当记录的真实数据占用的字节数不大于127(十六进制0x7F,二进制01111111)时,每个列对应的偏移量占用1个字节。
    • 当记录的真实数据占用的字节数大于127,但不大于32767(十六进制0x7F,二进制01111111)时,每个列对应的偏移量占用2个字节。
    • 当记录的真实数据占用的字节数大于32767时,属于行溢出,在本页中只保留前768个字节和20个字节的溢出页面地址。
  • Redundant行格式中NULL值处理
    因为Redundant行格式并没有NULL值列表,所以使用列对应的偏移量值的第一个比特位作为是否NULL的依据,该比特位也可以被称之为NULL比特位。也就是说在解析一条记录的某个列时,首先看一下该列对应的偏移量的NULL比特位是不是为1,如果为1,那么该列的值就是NULL,否则不是NULL。

  • 记录的真实数据
    不管该列使用的字符集是什么,只要是使用CHAR类型,占用的真实数据空间就是该字符集表示一个字符最多需要的字节数和字符串长度,例如使用UTF8字符集的CHAR(10)列占用的真实数据空间始终是30个字节。

Dynamic和Compressed行格式

Q.E.D.


Talk is cheap, show me the code.