文章目录
- InnoDB详解2
- 1 行格式
- 1 Compact行格式详解
- 1 变长字段长度列表(两个字节)
- 2 NULL值列表(1个字节)
- 3 记录头信息 (重点)
- 2 Dynamic行格式
- 2 页的上层结构
InnoDB详解2
1 行格式
规定每条记录是怎么存储的
MySQL 8默认行格式是Dynamic
InnoDB存储引擎设计了4种不同类型的`行格式`,分别是`Compact`、`Redundant`、`Dynamic`和`Compressed`行格式。
查看MySQL8的默认行格式:
mysql> SELECT @@innodb_default_row_format;
+-------------------------------------+
| @@innodb_default_row_format |
+-------------------------------------+
| dynamic |
+-------------------------------------+
1 row in set (0.00 sec)
也可以使用如下语法查看具体表使用的行格式:
SHOW TABLE STATUS like '表名'\G
1 Compact行格式详解
1 变长字段长度列表(两个字节)
在每条记录开始存入变长字段列表。没有变长字段就没有该项。小端存储。
如VARCHAR(M)、VARBINARY(M)、TEXT类型,BLOB类型,这些数据类型修饰列称为变长字段,变长字段中存储多少字节的数据不是固定的,所以我们在存储真实数据的时候需要顺便把这些数据占用的字节数也存起来。在Compact行格式中,把所有变长字段的真实数据占用的字节长度都存放在记录的开头部位,从而形成一个变长字段长度列表。
2 NULL值列表(1个字节)
为什么定义NULL值列表?
之所以要存储NULL是因为数据都是需要对齐的,如果没有标注出来NULL值的位置,就有可能在查询数据的时候出现混乱。如果使用一个特定的符号放到相应的数据位表示空置的话,虽然能达到效果,但是这样很浪费空间,所以直接就在行数据得头部开辟出一块空间专门用来记录该行数据哪些是非空数据,哪些是空数据,
用于标识哪些是null值,1位null ,0不为null。允许为null的字段才会有该记录。
明确表明该字段不为null值时,不会记录
如 一条记录 有 ABC 三个字段 A是主键 BC可以为null 该记录 A = 1 ,B= null ,C = 2, NULL值列表存的是 0 1 ,0表示C不是null,1表示 B是null。A是主键不能为null 所以不用存。
3 记录头信息 (重点)
delete_mask:当删除数据时,只需要将delete_mask标识位1。
占用1个二进制位。
-
值为0:代表记录并没有被删除
-
值为1:代表记录被删除掉了
被删除的记录为什么还在页中存储呢?
这些被删除的记录之所以不立即从磁盘上移除,是因为移除它们之后其他的记录在磁盘上需要重新排列,导致性能消耗。所以只是打一个删除标记而已,所有被删除掉的记录都会组成一个所谓的垃圾链表,在这个链表中的记录占用的空间称之为可重用空间,之后如果有新记录插入到表中的话,可能把这些被删除的记录占用的存储空间覆盖掉。
min_rec_mask:B+树的每层非叶子节点中的最小记录都会添加该标记,min_rec_mask值为1。
record_type:
这个属性表示当前记录的类型,一共有4种类型的记录:
0:表示普通记录
1:表示B+树非叶节点记录
2:表示最小记录
3:表示最大记录
heap_no:每条记录在此页中的位置,(Infimum )最小记录和(Supremum)最大记录的heap_no值分别是0和1,其他记录往后顺延。
**n_owned :**页目录中每个组中最后一条记录的头信息中会存储该组一共有多少条记录,作为 n_owned 字段。分组也叫slot槽,槽位记录的是最大记录的地址偏移,最后一条记录记录该组中有几条记录。
**next_record:**从当前记录的真实数据到下一条记录的真实数据的地址偏移量
删除操作
删除第二条记录时,先根据页目录二分查找到 槽位,然后进行遍历,遍历到第二条记录,delete_mask改为1,next_record改为0,第一条记录链接到第三条记录。该分组中 最后一条记录的n_owned由5变为4 (最小记录自成一组)
当数据页中存在多条被删除掉的记录时,这些记录的next_record属性将会把这些被删除掉的记录组成一个垃圾链表,以备之后重用这部分存储空间。
所以,不论我们怎么对页中的记录做增删改操作,InnoDB始终会维护一条记录的单链表,链表中的各个节点是按照主键值由小到大的顺序连接起来的
添加操作
2 Dynamic行格式
MySQL对一条记录占用的最大存储空间是有限制的,除BLOB或者TEXT类型的列之外, 其他所有的列(不包括隐藏列和记录头信息)占用的字节长度加起来不能超过65535个字节。
这个65535个字节除了列本身的数据之外,还包括一些其他的数据,以Compact行格式为例,比如说我们为了存储一个VARCHAR(M)类型的列,除了真实数据占有空间以外,还需要记录的额外信息。
如果该VARCHAR类型的列没有NOT NULL属性,那最多只能存储65532个字节的数据,因为变长字段的长度占用 2个字节,NULL值标识需要占用1个字节。
CREATE TABLE varchar_size_demo(
c VARCHAR(65532)
) CHARSET=ascii ROW_FORMAT=Compact;
如果有not null属性,那么就不需要NULL值标识,也就可以多存储一个字节,即65533个字节
我们可以知道一个页的大小一般是16KB,也就是16384字节,而一个VARCHAR(M)类型的列就最多可以存储65533个字节,这样就可能出现一个页存放不了一条记录,这种现象称为行溢出。
在Compact和Reduntant行格式中,存不下的数据就存一部分数据和其他数据的页地址,其他数据放到其他页中。
Compressed和Dynamic两种记录格式对于存放在BLOB中的数据采用了完全的行溢出的方式。如图,在数据页中只存放20个字节的指针(溢出页的地址),实际的数据都存放在Off Page(溢出页)中。
所以 Dynamic 与Compact的区别就是对行溢出处理不一样,dynamic只存溢出页的地址,compact还存了一部分数据。
2 页的上层结构
页中由一条条行记录构成,我们知道页之间是由双向链表连接的。如果连续的两个页之间真实地址相隔很远。磁盘寻道扫描速度很慢,
如果一个查询需要用到100个数据页,最坏情况下需要磁盘扫描100次。为了减少IO次数,所以有了区的概念
在数据扫描时,要先获取非叶子节点,如果将叶子节点与非叶子节点存放到一个区中,我们要的是叶子节点的数据,此时这个区中叶子节点数据很少,大部分是非叶子节点,获取真实数据时,还要扫描其他区,为了避免这种情况,引入了段的概念
也就是将叶子节点数据放到一个区中,非叶子节点放到一个区中,专门存叶子节点或者专门存非叶子节点的区就叫做段,创建一个索引时就会分配2个段。
表空间是一个逻辑概念。
碎片区
此时创建一个表,插入一条数据。若按照 区,段的划分,此时一条数据就要构建一个聚簇索引 要分配两个区。需要 16k * 64 * 2 = 2M的空间。(一个页16K,一个区有64页,索引有叶子节点与非叶子节点 要两个段)很浪费空间。
就引入了碎片区的概念。
碎片区属于表空间,该碎片去可以存段A的数据,也可以存段B的数据,就是什么都能存。
所以新建表的空间分配策略是,先找个碎片区分配空间,当一个表占用32个碎片区页面后,就会申请完整的区来存储数据。