对于MySQL来说,我们对它的认知并不多,只知道它的主要功能是存储数据。
但是 这些数据存放在哪里?以什么格式存放?MySQL怎么访问这些数据? 我们都不知道。
对于MySQL服务器来说,负责对表数据进行读取和写入工作的是存储引擎。在MySQL中有很多存储引擎,比如InnoDB、MyISAM等。不同的存储引擎存放数据的格式一般是不一样的。
对于MEMORY这个存储引擎来说,它不用磁盘存储数据,也就意味着服务器关闭后表的数据就消失了。
而InnoDB是MySQL的默认存储引擎,它是一个将表中数据存储在磁盘上的存储引擎。
InnoDB会将数据划分为若干个页,以页作为磁盘和内存之间的交互的基本单位。并且页的大小一般为16KB,也就是说,一次最少从磁盘中读取16KB的内容到内存中,一次最少把内存中16KB内容刷新到磁盘中。这样的设计,使得磁盘和内存之间数据交互不那么慢。
在MySQL中,系统变量innodb_page_size
表示了InnoDB的存储引擎中的页的大小,默认值为16384字节,也就是16KB。但是需要注意的是,在MySQL服务器运行过程中不可以更改页面大小!!!!
在InnoDB中,设计者设计了4种行格式
行格式,也称记录格式,是指记录在磁盘上的存放形式。
对于行格式,可以使用以下语法来指定行格式
CREATE TABLE 表名(列的信息) ROW_FORMAT=行格式名称;
ALTER TABLE 表名 ROW_FORMAT=行格式名称;
COMPACT行格式的示意图如下
由示意图可以看出,一条记录分为两大部分,分别是记录的额外信息和记录的真实数据
记录的额外信息包括三部分,分别是变长字段长度列表、NULL值列表和记录头信息。
什么是变长字段长度列表?
在MySQL中,有一些变长的数据类型,比如VARCHAR等。这些类型的列就可以被称为变长字段。变长字段存储的数据长度是不固定的,因此需要将这些数据占用的字节数存储起来。
在 COMPACT 行格式中,所有变长字段的真实数据占用的字节数都存放在记录的开头位置,从而形成一个变长字段长度列表,各变长字段的真实数据占用的字节数按照列的顺序逆序存放。
并且,并不是所有记录都有变长字段长度列表这部分,如果表中所有列都不是变长的数据类型或者所有列的值都是NULL的画,就不需要变长字段长度列表了。
对于这样的一个表
表的数据如下
+------+-----+------+------+
| c1 | c2 | c3 | c4 |
+------+-----+------+------+
| aaaa | bbb | cc | d |
| eeee | fff | NULL | NULL |
+------+-----+------+------+
对于第一条数据来说,变长字段的内容长度如下
列名 | 存储内容 | 内容长度(十进制表示) | 内存长度(十六进制表示) |
---|---|---|---|
c1 | ‘aaaa’ | 4 | 0x04 |
c2 | ‘bbb’ | 3 | 0x03 |
c3 | ‘d’ | 1 | 0x01 |
因此,变长字段长度列表字节串用十六进制表示效果如下
01 03 04
#注意:这里实际上是没有空格的,只是为了方便展示
第一条记录的变长字段长度存储如上图所示。
**像上面的变长字段的内容占用的字节数用1字节就可以表示(**也就是内容长度4,3,1可以分别使用十六进制0x04、0x03、0x01表示),但是如果变长字段的内容占用字节比较多,就需要使用2字节来表示了。
什么时候使用1字节表示?什么时候使用2字节表示呢?
InnoDB有属于它的规则。
M × W
M × W <= 255
,那么使用1字节来表示真实数据占用的字节数M × W > 255
L <= 127
,则使用1字节来表示真实数据占用的字节数L > 127
,则使用2字节来表示真实数据占用的字节数什么是NULL值列表
NULL值列表是统一管理一条记录中值为NULL的列。
它的处理过程如下:
因此,对于上面提到的这个表
+------+-----+------+------+
| c1 | c2 | c3 | c4 |
+------+-----+------+------+
| aaaa | bbb | cc | d |
| eeee | fff | NULL | NULL |
+------+-----+------+------+
两条记录的NULL值列表如下
第二条记录的06的由来是这样的:
在表中,c2列被NOT NULL修饰,所以不可能为NULL,只有c1和c3和c4可能为NULL。
但在第二条记录中,只有c1不为NULL,因此倒序排列后的NULL列表为110
110转化为十进制就是06
什么是记录头信息?
记录头信息是由固定的5字节组成,用于描述记录的一些属性,5字节也就是40个二进制位。
记录头信息中的各二进制代表的详细信息如下图所示
这里说的记录真实的数据除了自己定义的列的数据外,MySQL还会默认给每条记录加一些列。
列名 | 是否必需 | 占用空间 | 描述 |
---|---|---|---|
row_id |
否 | 6字节 | 行ID,唯一标识一条记录 |
trx_id |
是 | 6字节 | 事务ID |
roll_pointer |
是 | 7字节 | 回滚指针 |
InnoDB表的主键生成策略:
在上面提到的表中,数据如下
+------+-----+------+------+
| c1 | c2 | c3 | c4 |
+------+-----+------+------+
| aaaa | bbb | cc | d |
| eeee | fff | NULL | NULL |
+------+-----+------+------+
在表结构使用ascii字符集的时候,由于该字符集采用固定的一个字节来编码一个字符,是一个定长编码字符集,所以说CHAR不属于变长字段。
但是如果采用变长编码的字符集(如utf8表示1~3字节),即使使用的类型为CHAR,该类型对应的类也会存储到变长字段长度列表中!!!
在COMPACT 行格式中规定,采用变长编码字符集的CHAR(M)类型的列要求至少占用M个字节,意思就是说我们向列中存储一个空字符串也会占用10字节。
这样设计的意图是想以后更新该列的时候,如果新值大于旧值得字节长度但不大于10字节的时候,可以直接更新,而不用重新分配一个新的记录空间。
REDUNDANT 行格式的示意图如下
在REDUNDANT行格式中,没有了“变长”,意味着 REDUNDANT行格 会把该条记录中所有列(包括隐藏列)的长度信息按照逆序存储到字段长度偏移列表中
它的偏移量是通过采用两个相邻偏移量的差值来计算各个列值的长度的
继续以这个表的数据来说
+------+-----+------+------+
| c1 | c2 | c3 | c4 |
+------+-----+------+------+
| aaaa | bbb | cc | d |
| eeee | fff | NULL | NULL |
+------+-----+------+------+
第一条记录的字段长度偏移列表为
25 24 1A 17 13 0C 06
顺序排序后
06 0C 13 17 1A 24 25
这个是怎么计算出来的?
其他列以此类推即可!!!
REDUNDANT 行格式的记录头信息占用6个字节,总计48个二进制位
第一条记录中的头信息是
00 00 10 0F 00 BC
根据此可以得出各属性信息
对比COMPACT 行格式可以发现,REDUNDANT 行格式对了n_field
和1byte_offs_flag
这两个属性
但是缺少了record_type
这个属性
1byte_offs_flag
的值决定了每个列的偏移量是使用1字节还是2字节
那么
1byte_offs_flag
的值怎么选择的呢?
这是根据REDUNDANT行格式记录的真实数据占用的大小来判断的
REDUNDANT行格式中将列对应的偏移量值得第一个比特位作为是否为NULL的依据,这个比特位也成为NULL比特位。
这就是为什么只要记录的真实数据大于 127 (十六进制0x7F,二进制01111111)时候,就采用2字节来表示一个列对应的偏移量了。
+------+-----+------+------+
| c1 | c2 | c3 | c4 |
+------+-----+------+------+
| aaaa | bbb | cc | d |
| eeee | fff | NULL | NULL |
+------+-----+------+------+
对于这里的第二条记录,长度偏移列表为
A4 A4 1A 17 13 0C 06
顺序排序就是
06 C 13 17 1A A4 A4
由于c3列为定长类型,所欲NULL值也将占用记录的真实数据部分,所以使用0x00字节填充
而如果是变长数据类型,则不再记录的真实数据占用任何存储空间。
对于REDUNDANT行格式来说,不管使用的字符集是什么,只要使用CHAR(M)类型,该列的真实数据占用的内存空间大小就是该字符集表示一个字符最多需要的字节数和M的乘积。
比如utf8字符集的CHAR(10)类型的列,其真实数据占用的存储空间大小始终为30字节**。这样做,在更新数据的时候,就不需要申请新的存储空间了。**
在COMPACT 行格式和REDUNDANT 行格式中,对于占用内存空间非常多的列,在记录真实数据处只会存储该列的一部分数据,将其他数据分散存储在几个其他页中。
并在记录真实数据处用20字节存储指向这些页的地址。
DYNAMIC行格式和COMPRESSED行格式在处理溢出列的时候,它们不会在记录的真实数据处存储该溢出列真实数据 的前 768 字节,而是把该列的所有真实数据都存储到溢出页中,只在记录的真实数据处存储 20 节大小的指向溢出页的地址
而COMPRESSED不同于DYNAMIC的一点在于会使用压缩算法进行压缩,以节省空间。
除了处理溢出的时候会于COMPACT 行格式不一样外,其他都于COMPACT 行格式很像。
参考:
- 《MySQL是怎样运行的:从根儿上理解 MySQL》
- 《MySQL是怎么运行的:从根儿上理解MySQL》(1-5)学习总结_月亮的-影子的博客-CSDN博客_从根上理解mysql
- mysql是怎样运行的-从根儿上理解mysql学习笔记(一)_spencersong的博客-CSDN博客_mysql 是怎样运行的