物理数据存储格式
行格式
什么是行格式
我们插入的一条记录在磁盘中的存放方式称为行格式(也叫记录格式),InnoDB 共具有 4 种不同类似的行格式 。
指定某个表的行格式方法
CREATE TABLE 表名 (列的信息) ROW_FORMAT=行格式名称 ALTER TABLE 表名 ROW_FORMAT=行格式名称
示例:
use dpl_test; create table row_format_demo ( id bigint primary key, name varchar(32) ) row_format = compact -- 四种格式 compact | redundant | dynamic | compressed
默认行格式:
SELECT @@innodb_default_row_format; -- 默认行格式 默认为 dynamic -- 更改默认行格式 SET GLOBAL innodb_default_row_format = DYNAMIC;
dynamic
动态行格式 该行格式允许长度可变,所以会根据情况来决定是否需要更多空间,5.7后的默认行格式
compact
减少了存储行间,官网说大约20%,但是增加了cpu的负荷。导致一些查询的性能问题
大致格式:变长字段的长度列表,null值列表(长度为8n),数据头[隐藏字段],col1的值,col2的值。。。
redundant
冗余行格式,主要是旧版本mysql的兼容, 数据和行索引信息分开存储,某些查询操作会快,但是需要额外的空间,所以是之前老版本的格式设计.
compressed
压缩行格式 对COMPACT进行了压缩,减少了存储空间使用,比如text 长文本 会进行压缩,但是检索的时候,必须进行解压,牺牲了cpu。
变长字段的存储
因为变长字段的内容不固定,所以无法判断数据要从何处截断,因此在数据的头部保存了变长字段的长度列表。多个变长字段按照字段顺序逆序放入变长字段的长度列表中。不考虑为null的列。
NULL值的存储
对于所有的NULL值,是通过二进制的bit位来存储,一行数据如果有多个字段的值为NULL,那么这些字段的NULL会以bit位的形式存放在NULL值列表中。0表示不是NULL,1表示是NULL,同样也是逆序存放.需要注意的是,不允许为NULL的列是不考虑的
。
数据头
bit位 | 名称 | 作用 |
---|---|---|
1 | 预留位 | 无 |
1 | 预留位 | 无 |
1 | delete_mask | 删除标志位 |
1 | min_rec_mask | B+树的每一层非叶子节点的最小值会有这个标志 |
4 | n_owned | 拥有的记录数 |
13 | heap_no | 当前记录在记录堆的位置信息 |
3 | record_type | 当前记录的类型,0:普通;1:B+树非叶子节点;2:最小值数据;3:最大值数据 |
16 | next_record | 下一条数据的指针 |
隐藏字段
- DB_ROW_ID:行唯一标识,在没有指定主键和Unique key唯一索引的时候,会以他作为主键
- DB_TRX_ID:事务相关
- DB_ROLL_PTR:回滚指针,用于事务回滚
示例
红色的列表示不允许为空
varchar(10) | varchar(20) | varchar(5) | char(2) | char(3) | 可能格式 |
---|---|---|---|---|---|
hello | nice | a | zx | cc | 0x01,0x04,0x05[头字段]hello nice a zx cc |
ppt | word | flash | d | z | 0x05,0x04,0x03[头字段]ppt wprd flash d z |
jack | NULL | cc | ps | NULL | 0x02,0x04[头字段]jack cc ps |
tom | 3 | mg | NULL | KG | 0x02,0x01,0x03[头字段]tom 3 mg KG |
紧凑的意义
节省空间?
读取的过程
- 示例样本(选自上方)
0x02,0x01,0x03[010000001000011111]tom 3 mg KG - 先读取出变长字段长度列表和NULL值列表,分析得到几个变长字段以及哪几个字段是NULL。因为MYSQL自己定义的列以及类型自己最清楚哪些列是变长哪些列允许NULL
- 第一个字段不允许为空所以不会出现在NULL值列表中,是变长类型所以从变长列表中取出0x03,就去字段列表中读取3个字符的长度,得到tom
- 第二个字段为变长允许为空,所以读取NULL值列表知道不为空,在读取变长列表得到长度为0x01,所以读取1个字符的长度得到3
- 第三个字段为变长允许为空,所以读取NULL值列表知道不为空,在读取变长列表得到长度为0x02,所以读取2个字符的长度得到mg
- 第四个字段为定长允许为空,所以直接读取NULL值列表知道为空,所以直接为null
- 第五个字段为定长允许为空,直接读取NULL值列表知道不为空,所以直接读取固定的3个长度得到KG 。(这里KG后面还有一个空格补充长度)
行溢出
因为每行数据都是存放在一个数据页中的,一个数据页是16KB,如果一行数据的大小超过了数据页的大小。比如一个字段是VARCHAR(65532), 最多可以放65532个字符,65532个字符至少也是65532b≈64kb>>16kb。
这个时候就会在那一页存放你的数据,然后特别长的字段中,只会包含部分数据,同时还包含一个20个字节的指针,指向其他的数据页
,用于把这些数据页用链表串联起来,存放超大数据。
影响
当出现行溢出
时会查询溢出的页导致多余的磁盘I/O也会导致查询时间增加,所以建议在查询时只查询需要的列,对于可能溢出的列又用不到就不要去查了。
数据页的拆分
数据页16kb的大小实际上被拆分成了多个部分,包括
- 文件头(38b)
- 数据页头(56b)
- 最小记录和最大记录(26b)
- 多个数据行
- 空闲空间
- 数据页目录
- 文件尾部(8b)
数据区与数据组
在磁盘上,一个表空间的数据文件中可能包含多个数据页,为了便于管理,引入了数据区的概念。一个数据区对应着64个连续的数据页,每页16kb,所以一个数据区是1MB。256个数据区划分为1组(extent)。所以1组是256MB。
第一个数据区特殊的3页
一个表空间的第一个数据区的前3个数据页是固定的,存放描述性信息。
- FSP_HDR
- IBUF_BITMAP
- INODE
其他数据区特殊的2页
同理也是存放描述性信息
- XDES
- 未知
一个口述的数据插入流程
- 根据表名找到对应的表空间,定位到对应的磁盘文件
- 从磁盘文件中拿到一个extent组,从里面找出一页数据页
- 加载数据页到Buffer Pool