zl程序教程

您现在的位置是:首页 >  数据库

当前栏目

【MySQL】InnoDB行格式

2023-02-18 16:32:46 时间

1)数据存储形式

首先明确在 innodb 引擎中数据是以页为基本单位读取的,而一个页中又包含多个行数据,那么对应地就会有不同的行格式来存储数据,innodb 中的行格式有四种:compact、redundant、dynamic、compressed。redundant 是 5.0 之前用的行格式,这里就不记录了。

2)compact 行格式

可以看到 compact 行格式中将一行分成了两个部分,一个是真实数据的存储,一个是一些记录的信息。接下来一个一个看。

① 变长字段长度列表在 MySQL 中有 char 和 varchar 两种字符串类型,他们的区别是 varchar 是变长的类型,对于一列二进制流,我们通过变长字段长度列表就可以得到真实长度。在这里会逆序地存储变长字段的真实长度,真实长度用 1 个/2 个字节来存储,同时将长度的第一个二进制位作为标志符,如果为 0 则说明长度是 1 个字节,如果为 1 则说明长度是 2 个字节,这也就可以解释为什么 varchar 只能存储最大 65535 个字节,也解释了为什么真实长度在 127 以下就可以用一个字节存储。这里不是说 char 类型就一定不会用到该列表,对于定长的字符集来说,char 只要定义之后相应的内存空间分配就固定下来了,也就无需去记录长度,但对于变长的字符集(utf8、utf8mb4)来说,即使是 char 类型的数据也需要记录在该列表中。拿 utf8 来讲,其允许1~3个字节的数据存储,那么对于char(10)来说就允许存储10~30个字节的数据,那么具体多少个字节还需要变长字段长度列表去记录,同时与 varchar 不同的是,char(10)要求至少占 10 个字节,即使我们插入 1 个字节的数据,也会使用空格补全到 10 个字节,避免更新时产生碎片的问题。当然这一块并不是必须存在的,如果表中没有变长字段则不会有这个列表。

② NULL 值列表

对于允许存在 NULL 值的列如果我们把 NULL 值也存储在真实数据中那么会占据较多的空间,其实对于 NULL 值的存储只有是或不是两种可能,因此可以用一个二进制位来表示一个列是否为 NULL,那么对应到行格式中就是 NULL 值列表。

和变长字段长度列表一样,这里的存储也是逆序的,如果一个列是 NULL 则该位为 1,否则为 0,如果出现不足整数个字节的二进制数还需要在高位补 0,例如存储 3 个 NULL 值则需要在头部补 5 个 0。当然这里需要注意,NULL 值列表只存储那些可以为 NULL 的列,如果表中没有允许 NULL 值的列则该列表也不会存在。

③ 记录头信息

记录头信息固定为 5 个字节 40 个二进制位组成,主要有:

delete_mark:行删除标记,在 innodb 中对于行数据的删除并不会马上去刷盘,而是先打上一个标记,待后续刷盘时机到了再把脏页刷入。 min_rec_mark:最小记录标记,用来标记非叶子节点的最小记录。 record_type:记录类型,0 代表普通记录,1 代表非叶子结点记录,2 代表最小记录,3 代表最大记录。 n_owned:因为一个行可能是非叶子结点,所以用这个字段来代表其下的子节点数目。 next_record:指向下一条记录的指针,这里可以对应到 B+树的结构特点。

④ 真实数据

在 innodb 中对于数据的存储不只是我们定义的那些列,还包括一些引擎自动生成的隐藏列,其中包括 db_row_id,db_trx_id、db_roll_ptr。

如果我们定义的表中既没有主键也没有唯一字段,那么 innodb 会自动帮我们创建一个 db_row_id 充当主键,因为 innodb 是索引组织表,必须要有主键索引,该字段占 6 个字节,如果我们自己定义了主键或者唯一键则可以节省该空间占用。

db_trx_id 是用来标识当前事务的 id 的,db_roll_ptr 是一个指向回滚事务链的指针,这两个字段搭配应用在事务与 MVCC 中,db_trx_id 占 6 个字节,db_roll_ptr 占 7 个字节。

3)dynamic 与 compressed 行格式

dynamic 与 compact 基本相同,只不过对于大长度字符串的处理略有不同。compact 会记录前 768 个字节,其余字节存储到其他页,之后用一个指针指向它,而 dynamic 则会将全部数据都存储到其他页,之后用一个指针去指向它。 compressed 于 dynamic 的差别就是,compressed 采用了压缩算法,让行数据更加节省内存。

4)对于大字符串溢出的处理

MySQL 限制一个行中除了 text、blob 之外的其他所有列合起来最大只能存储 65535 个字节,如果超过该值会报错,只能使用 blob 或者 text 类型来存储。那么对于 varchar 类型的长字符串来说,除了真实数据之外还需要有 2 个字节来存储字段长度,1 个字节来存储是否为 NULL(如果列不允许为 NULL 则不需要),那么实际上 varchar 最大只能存储 65533 个字节(在一个表中只有一个列的情况下),那么允许存储的最大字符数目就除以字符集单个字符的最大字节数即可。 前面也讲到了,innodb 中数据存储的基本单位是页,一个页只有 16KB(16384 字节),那么如果字符串的数据量大于这个值怎么办呢?溢出到其他页处理。 在 compact 和 redundant 中,如果行的数据超过了 16384 字节,那么在本行中只会存储其中的前 768 个字节,将其他数据放到其他的页中(溢出页),再用 20 字节的指针指向其他页。在 dynamic 和 compressed 中只会存储这个 20 字节的指针,数据都放到溢出页去。 那么存储多大的数据才会让行溢出呢?innodb 中规定了一个页最少要存储两条记录,除了存储行数据之外,每个页还要有 136 个字节来存储记录信息,同时每个行需要有 27 个字节来存真实数据以外的信息,那么最终每行的真实数据大小的最大值就是 8097 字节 ( (16384-136) / 2 - 27 ),也就是说如果列数据大于 8097 字节将会导致行溢出。