一、页
InnoDB将数据存储在磁盘上,真正处理数据的行为发生在内存里。所以操作数据时,需要将数据在磁盘与内存来回交换,Innodb采用页作为交换的基本单位,一页一般16KB大小。也就是说Innodb一次最少从磁盘里读取16KB的数据到内存中,同样最少写入16KB的数据到磁盘。
二、InnoDB的行格式
平时,我们哪知道Innodb操作的是一页一页的数据,看到的都是一行一行的。这种一行一行的记录简称‘行格式’吧。它们也是有固定的格式,Innodb的设计者设计了4种行格式,分别是Compact、Dynamic、Compress和Redundant。
1、Compact行格式

一行记录分为额外信息与真实数据两部分,在进一步。
1.1、变长字段的长度列表
当列的格式是变长格式时,Innodb将真实内容与数据的真实长度分开存放,例如varchar(M),text等。当格式为定长格式(例如char(M))时,则不会将该列的长度信息保存在此处。但是这种情况仅适用于定长字符集,也就是一个字节就代表一个字符;如果字符集选用变长字符集时,例如utf8使用1~3个字节表示一个字符时,char(M)格式的列也需要将真实长度列表记录在额外信息里。对于char(M)采用变长字符集时,即使此列不存任何值,也至少占用M个字节,这是怕将来更新内容时如果新值长度小于M就能就地更新,不用重新分配空间。
另外列的真实长度在此处保存的顺序是逆序存放的。并且如果某列的真实长度超过一个字节时,就会用两个字节表示。如果所有的列都采用定长字符集、定长格式时,变长字段的长度列表就没有必要存在了。
保存一列的真实长度时,会根据字符数量M、字符集字节数W、真实记录长度L选择一个字节还是两个字节。W*M<=255时用一个字节;W*M>255时,如果L<=127,用一个字节,L>127时用两个字节。
1.2、NULL值列表
当列的定义可为NULL时,就会在对应的bit上标记为1,同样是逆序存放。如果所有列定义都不能为null时,那么NULL值列表同样没有必要存在。NULL值列表规定必须使用整个字节表示,当你有一列格式可为null时,NULL值列表会使用一个字节保存,其他位补0。当你有9列可为null时,将使用2个字节保存。
1.3、记录的头信息
字段名 | 大小 bit | 描述 | 作用 |
delete_mask | 1 | 标记是否删除 | 只是标记为删除,这条记录的空间并没有直接被回收,还可以被重新利用。并且这种状态很适合进行事务处理 |
min_rec_mask | 1 | 用来标记B+树非子节点的最小记录 | 用来建立B+树索引的 |
n_owned | 4 | 表示当前拥有的记录数 | 每页可能存储很多条记录,于是页又把记录进行分组。每组生成一个目录slot,将目录slot存在页的page_directory字段里。最小记录为单独的一组,n_owned就是1咯。每组的最后一条记录的n_owned记录的就是本组的记录数量 |
heap_no | 13 | 表示当前记录在记录堆中位置信息 | 记录在页中的相对位置。最小记录与最大纪录分别为0,1。我们插入的第一条记录就从2开始 |
record_type | 3 | 标记记录类型 | 0:普通记录(就是行数据) 1:表示B+树非叶子节点记录 2:最小记录 3:最大记录 |
next_record | 16 | 下一条记录的相对位置。行记录是以主键组成的递增的单向链表 | 你也懂了吧 |
1.4、记录的真实数据
真实数据除了我们定义的列,MySQL还可能自动生成三个隐藏列:
列明 | 是否必须 | 描述 |
DB_ROW_ID | 否 | 行记录的唯一标识。如果我们自定义了主键或唯一索引,此列就不需要存在了 |
DB_TRX_ID | 是 | 事务ID。 |
DB_ROLL_PTR | 是 | 回滚 |
每一个行记录都有这三列(如果明确指定主键或唯一索引,DB_ROW_ID就不存在)。
剩下两个隐藏列是InnoDB实现行锁的关键属性
2、Redundant行格式
这种格式是MySQL5.0之前用的,很老套了。大致如下

与Compact格式相比,没有了NULL值列表。
2.1、字段长度的偏移列表
redundant行格式会把该条记录中所有列(包括隐藏列)的长度信息都按照逆序存储到字段长度的偏移列表。并且保存的是两个相邻列真是内容长度的差值。
并且记录的头信息也略有变化:
字段名 | 大小 bit | 描述 |
delete_mask | 1 | 标记是否删除,删除的记录并不会立马被回收哦 |
min_rec_mask | 1 | 用来标记B+树非子节点的最小记录 |
n_owned | 4 | 表示当前拥有的记录数 |
heap_no | 13 | 表示当前记录在记录堆中位置信息 |
n_field | 10 | 表示记录中列的数量 |
1byte_offs_flag | 1 | 标记字段长度偏移列表中每个列对应的偏移量是使用1字节还是2字节表示的 |
next_record | 16 | 下一条记录的相对位置。行记录是以主键组成的递增的单向链表 |
redundant行格式不会在乎什么可变长度字段,一开始分配M*W个字节空间。
3、Dynamic与Compress行格式
这两种行格式与Compact模式相同。只是在处理行溢出现象时,处理方式不同。
3.1、行溢出
一页的大小是16KB,当一条的记录大于16KB时就会出现行溢出现象。例如varchar(M)最多占用65535个字节,相当于64KB。
Compact与Redundant会保存真实的记录内容,但对于行溢出,只会保存真实记录的前768字节,再使用20字节保存溢出页地址,溢出页里存放数据。Dynamic与Compress行格式则只保存堆(页)地址,将真实内容全部保存在堆(其实就是页)中,其中Compress还会对真实内容进行压缩。
3.2、行溢出临界点
MySQL规定一页中最少存两条记录。
如果一行只有一列,一页16384个字节,最少两条数据。每一页除了数据列,还有132个字节的关于页的其他信息,每个记录的额外信息27字节。132+2*(27+n)<16384,得出这条列的总大小要小于8099个字节。如果这列超过此值,就可能成为溢出列。真实情况是我们会有很多列,计算方式就得另算了。
所以推荐不关注临界点,但是单个列数据过长时,就要考虑有溢出的危险。