InnoDN存储引擎是面向行的,即数据都是一行行存储的,下面时两种主要的行记录格式。

Compact行记录格式

Compact行记录是在MySQL 5.0时被引入的,其设计目标是能高效存放数据。简单来说,如果一个页中存放的行数据越多,其性能就越高。Compact行记录以如下方式进行存储:
image.png

  • 变长字段长度列表:存放变长的字段类型的列的长度,按照列顺序逆序放置。这里的可变类型指VARCHAR、TEXT、BLOB以及多字符集情况下(如utf8)的CHAR,最大长度不超过2字节。
  • NULL标志位:标志了改行数据中是否有NULL值,有则用1表示,该部分占1字节。
  • 记录头信息:记录头信息占40未,即5字节,每一位的含义如下:

image.png

  • 列数据:列数据由用户自定义列数据和隐藏列数据组成。用户自定义列数据就是用户根据业务需求自己定义的字段,要注意的是如果改行的列数据为NULL,则实际存储不占任何空间;而隐藏列包括rowid、事务ID列、回滚指针列,rowid是用户没有指定主键时自动创建的一个6字节的列,事务ID列和回滚指针列分别占4字节和7字节。

    Redundant行记录格式

    Redundant是MySQL 5.0版本之前InnoDB的行记录存储方式,MySQL 5.0支持Redundant是为了向前兼容性。Redundant行记录以如下方式存储:
    image.png

  • 字段长度偏移列表:同Compact一样是按照列的顺序逆序放置的

  • 记录头信息:不同于Compact行格式,Redundant行格式固定占用6个字节(48位),每位的含义如下:

image.png

注意: n_fields值代表一行中列的数量,占用10位,这也很好地解释了为什么MySQL一个行支持最多的列为1023

  • 列数据,在列数据中和Compact不同的室对NULL值得处理,Redunant对于VARCHAR为NULL同样不占存储空间,但是对于CHAR类型得NULL值则需要占存储空

    说明: 在MySQL中,VARCHAR是存储变长长度的字符类型,而CHAR是存储固定长度的字符类型。然后,在不同的字符集下,CHAR类型列内部存储的可能也是不定长的数据类型,因此一认为在多字符集下二者实际行存储基本是没有区别的。

行溢出数据

MySQL 对一条记录占用的最大储存空间是有限制的,在MySQL的设定中,单行数据最大能存储65535字节的数据,除了 BLOB 和 TEXT 类型之外,其他所有列 (不包括隐藏列和记录头信息))占用的字节长度不能超过 65535 个字节,当记录长度超过限制时,MySQL 会建议使用 TEXT 或 BLOB 类型。储存上限 65535 个字节不仅包含本身的数据,还包含一些其他数据,以 VERCHAR 类型为例,共需要 3 部分储存空间:

  • 真实数据
  • 真实数据占用的字节长度
  • NULL 值标识,NOT NULL 列没有这部分

    注意:

    • 当使用 ascii 字符集时:如果 ‘真实数据占用的字节长度’ 占用两个字节,NULL 值标识占用一个字节,则真实数据最多只能存储 65532 个字符
    • 当使用 utf8 字符集时:如果 ‘真实数据占用的字节长度’ 占用两个字节,NULL 值标识占用一个字节,则真实数据最多只能存储 21844 (65532 / 3) 个字符

由于 MySQL 中以页为基本单位来管理储存空间的,所有的记录都会被分配到页中。由于一个页一般为 16KB (16384 个字节),而一个 VERCHAR 最多可以存储 65532 个字节,所以会出现一个页存放不下一条记录的情况,造成行溢出不仅是 VERCHAR,BLOB 和 TEXT 也会发生行溢出。InnoDB存储引擎才有行溢出数据来解决这个问题,如下图所示:
image.png
在InnoDB中,如果一个可变字段存储的数据长度超过768字节,就需要将多余的数据(溢出数据)存储到溢出页,这种数据页的类型为Uncompressed BLOB页。而在叶子节点中保存的真正数据就是前768字节的前缀数据,之后是偏移量,指向溢出页。

注意: 行溢出机制还可以保证B+树索引的意义,参考下面文章第七点: https://www.cnblogs.com/ZhuChangwu/p/14035330.html#%E4%B8%89mysql%E5%8D%95%E8%A1%8C%E8%83%BD%E5%AD%98%E5%A4%9A%E5%A4%A7%E4%BD%93%E9%87%8F%E7%9A%84%E6%95%B0%E6%8D%AE