InnoDB简介
**InnoDB**是一种将数据存储到硬盘的一种存储引擎。
InnoDB 行格式
5.7之前存在**COMPACT**、**REDUNDANT**、**DYNAMIC**、**COMPRESSED**形式。
### 指定行格式的语法
mysql> create table record_format_demo(
-> c1 varchar(10),
-> c2 varchar(10) not null,
-> c3 char(10),
-> c4 varchar(10)
-> ) charset = ascii row_format = compact;
Query OK, 0 rows affected (0.05 sec)
INSERT INTO `test`.`record_format_demo`(`c1`, `c2`, `c3`, `c4`) VALUES ('aaa', 'bbb', 'cc', 'd'),('eee', 'fff', NULL, NULL);
COMPACT行格式
-
记录的额外信息
-
变长字段长度信息
在MySQL中,可变长的数据类型,例如:VARCHAE、VARBINARY、各种TEXT、BLOB类型等变长字段。变长字段存储多少字节的数据也是不固定的,为了清楚的知道,需要存储一下其真是的长度,也就是说这样的变长字段可以分为两部分。
-
真正的数据内容
-
该数据占用的字节数
在COMPACT中,所有变长字段的真实数据占用的字节数都存在在记录的开头位置,从而形成一个变长字段长度列表,各变长字段的真实数据占用的字节数按照列的 顺序逆序存放。
record_format_demo表中的各个列的值使用的都是ascii字符集,每个字符值需要一个字节来编码。
列明 | 存储内容 | 内容长度 (十进制) | 内容长度 (十六进制) |
c1 | 'aaaa' | 4 | 0x04 |
c2 | 'bbb' | 3 | 0x03 |
c4 | 'd' | 1 | 0x01 |
所有变长字段使用长度列表的字节串用十六进制表示的效果(实际上,真实存储并没有空格分隔):
01 03 04
对于变长字段的内容占用的字节数比较多,可能就需要用2个字节来表示。至于使用一个字节还是来两个字节来表示,InnoDB有如下规定,规则引入 W,M, L这几个符号:
-
假设某个字符集中最多需要W字节来表示一个字符,例如assic是1,utf8是3,gbk是2.
-
对于变长类型VARCHAR(M)来说,这中类型表示能存储最多M个字符,所以这种类型能表示的字符串最多占用的字节数是M*W.
-
假设改变长字段实际存储的字符串占用的字节数是L。
-
如果M*W<=255, 那么使用1个字节来表示真实数据占用的字节数。
-
如果M*W > 255, 分两种情况处理
-
如果L<= 127, 则用1字节来来表示真实数据占用的字节数;
-
如果L>127, 则用2字节来表示真实数据占用的字节数。
InnoDB使用变长字段的第一个二进制作为表示:如果该字节的第一位为0,该字节就是一个单独的字节长度,如果该字节的第一位为1,该字节就是半个字端长度,需要后面一个字节组合成该字段的长度。
填充改完两条数据的记录的格式如下图。
-
NULL值列表
在COMPACT行格式吧一条记录中的NULL的列统一管理起来,存储到NULL值列表中。他的处理过程如下:
-
首先统计表中允许存储NULL的列有哪些。
-
如果表中没有允许存储NULL的列,则NULL列表也就不存在了,否则将每个允许存储NULL的列对应一个二进制,而二进制位按照列的顺序逆序排列。
-
二进制的值为1时,代表该列的值为NULL;
-
二进制的值为0时,代表该列的值不允许为NULL。
-
如果使用的二进制位个数不是整数个字节,则在字节高位补0。
-
对于第一条记录,它的NULL值的二进制如图所示:
-
对于第一条记录,它的NULL值的二进制:
-
记录头信息
用于描述记录的记录头信息,它是由固定的5个字节组成。也就是40个二进制位,不同的位代表不同的意思。
名称 | 大小(位) | 描述 |
预留位1 | 1 | 没有使用 |
预留位2 | 1 | 没有使用 |
delete_flag | 1 | 标记该记录是否被删除 |
min_rec_flag | 1 | B+树的每一层非叶子节点中最小的目录记录项都会添加该记录 |
n_owned | 4 | 一个页面汇总的记录会被分成若干个组,每个组有一个记录是“带头大哥”,其余的记录都是“小弟”。“带头大哥”记录的n_owned的值代表该组中所有的记录条数,“小弟”记录的n_owned的值为0 |
heap_no | 13 | 表示当前九路在页面堆中的相对位置 |
record_type | 3 | 表示当前记录的类型,0表示不同记录,1表示B+树非叶子节点的目录记录,2表示Infimum记录,3表示Supremum记录 |
next_record | 16 | 表示下一条记录的相对位置 |
record_format_demo的两条记录如下
2. 记录的真实数据
MySQL会为每个记录默认地添加一些列(也被称为隐藏列)
列名 | 是否必须 | 占用空间 | 描述 |
row_id | 否 | 6字节 | 行ID,唯一标识一条记录 |
transaction_id | 是 | 6字节 | 事务ID |
roll_pointer | 是 | 7字节 | 回滚指针 |
InnoDB表的主键生成策略:
1)优先使用用户自定义的主键作为主键;
2)如果用户没有定义主键,则选取一个不允许存储NULL值的UNIQUE键作为主键
3)如果2)不满足,InnoDB会默认添加一个名为row_id的隐藏列作为主键。
-
记录使用的ascii,所以0x61616161表示的‘aaaa’,0x626262表示为‘bbb’;
-
c3列为char(10),及时实际存储的是‘cc’,但是整个列依旧暂用10字节的空间。
-
第二条记录中c3和c4列的值都为NULL,他们被存储在了前面的NULL值列表出,真实数据处不在冗余存储了,从而节约空间。
3.CHAR(M)列的存储格式
在compact行格式下只会把定长类型的列的长度逆序存到变长字段长度列表中,而ASCII编码是定长编码字符集,而gbk中一个字符表示1-2个字节和utf-8中一个字符表示1-3个字节,都是不定长编码字符集。在示例表中C3列是char(10),那么C3的长度也会被存储到变长字段长度列表中。
对于char(M)类型的列来说,当列采用的是定长字符集时,该列占用的字节数不会被加到变长字段长度列表中,而如果采用变长字符集时,该列占用字节数会被加到变长字段长度列表。
变长字符集的char(M)类型的列要求至少占用M个字节,而varchar(M)没有这样的要求。比方说对于使用utf-8字符集的char(10)来说,该列存储的数据的长度范围是10-30个字节。那么即使存储一个空字符串也会占用10个字节的空间,这么做的目的主要是方便以后更新该字段时,并且更新的字段长度小于10字节时可以直接更新而不需要重新分配一个新的记录空间避免之前的记录空间成为碎片。
REDUNDANT格式
Redundant是MySQL 5.0版本之前InnoDB的行记录存储方式。
-
字段长度便宜表
他与变长字段长度列表有两处区别:
-
没有“变长”两字,意味着:长度信息都按照逆序存储到字段长度的偏移列表
-
多了“偏移”,采用相邻两个的值,来计算各列的长度。
比如第一条记录的字段长度偏移列表:
25 24 1A17 13 0C 06
因为它是逆序排放的,所以按照的顺序排列:
06 0C13 17 1A 24 25
按照两个相邻偏移量的差值来计算各个列值的长度的意思就是:
-
第一列(row_id)的长度就是0x06个字节,也就是6字节
-
第二列(trx_id)的长度就是(0x0C-0x06)个字节,也就是6字节;
-
第三列(roll_pointer)的长度就是(0x13-0x0C)个字节,也就是7字节;
-
第四列(c1)的长度就是(0x17-0x13)个字节,也就是4个字节;
-
第五列(c2)的长度就是(0x1A-0x17)个字节,也就是3个字节;
-
第六列(c3)的长度就是(0x24-0x1A)个字节,也就是10个字节;
-
第七列(c4)的长度就是(0x25-0x24)个字节,也就是1个字节;
-
记录头信息
名称 | 大小 | 描述 |
预留位1 | 1 | 暂未使用 |
预留位1 | 1 | 暂未使用 |
delete_flag | 1 | 标记该记录是否被删除 |
min_rec_flag | 1 | B+树的每层非叶子节点中的最小记录的标志位 |
n_owned | 4 | 一个页面汇总的记录会被分成若干个组,每个组有一个记录是“带头大哥”,其余的记录都是“小弟”。“带头大哥”记录的n_owned的值代表该组中所有的记录条数,“小弟”记录的n_owned的值为0 |
heap_no | 13 | 表示当前九路在页面堆中的相对位置 |
n_field | 10 | 表示记录中列的数量 |
1byte_offs_flag | 1 | 标志每个列对应的偏移量是使用1字节还是2字节 |
next_record | 16 | 下一条记录的绝对位置 |
-
记录头中的1byte_offs_flag的值是怎么选择的
-
真实数据占用的字节数不大于127时,使用1个字节,此时它的值为1
-
真实数据大于127且不大于32767时,使用2个字节,此时它的值为0
-
真实数据大于32767时,部分存放溢出页
-
NULL值处理
当某一列的数据为NULL时,对应偏移量的第一位1,否则就不是NULL,所以当记录大于127时,就使用两个字符。
-
CHAR(M)列的存储格式
该列的真实数据占用的存储空间大小就是该字符集一个字符最多需要的字节数和M的成绩。
溢出列
-
溢出列
一个页大小一般是16K,也就是16384字节,而现实中一个列时会大于16384字节,所以一页存不了一条记录,这样就很尴尬。
对于大于16384的数据,一页在记录的真实数据,只存储前768字节,剩下的会存20字节,指向真正存储数据的页。
-
产生溢出页的临界点
MySQL中规定一个页总至少存放两记录,那么每条记录至少包含多少字节的数据才会需要溢出页
-
每个页除了存放我们的记录以外,业务要存储一些额外的信息,加起来一共需要132字节的空间。
-
对于只有一个列的页,每个记录需要的额外信息时27字节,构成如下
-
2个字节用于存储真实数据长度;
-
1个字节用于存储NULL;
-
5字节大小的头信息
-
6个字节的row_id列;
-
6个字节的trx_id列;
-
7个字节roll_pointer列。
假设一个列的真实数据占用的字节数为n,如果要满足该列不发生溢出现象,132+2*(27+n)<16384 得出 n < 8099。
DYNAMIC行格式和COMPRESSED行格式
MySQL 5.7 默认航哥时就DYNAMIC,他们与COMPACT的区别就是在对溢出列的处理:
他们不会在 溢出列存放前768字节真实数据,而是把苏哦有真实数据都存放到溢出列中。
COMPRESSED不同于DYNAMIC的一点的是,COMPRESSED对页面进行压缩,以节省空间。