路漫漫其修远兮,吾将上下而求索。
引言
当我们根据主键id查询一条数据,如:select * from student where id = 1Innodb存储引擎从磁盘拿取对应数据是一条吗?我们知道磁盘IO读写性能是很差的,和内存读写相差好几个数量级,所以Mysql根据B+树结构,把数据划分多个页按页来读取,也就是常说的数据页,并且以页当做磁盘和内存交互最小单位。
我们平时是以记录为单位来向表中插入数据的,这些记录在磁盘上的存放方式也被称为 行格式 或者 记录格式 。InnoDB存储引擎行格式主要分为四种Compact,Redundant,Dynamic和Compresed。
一、Compact行格式
1. 指定Compact行格式语法
CREATE TABLE 表名 (列的信息) ROW_FORMAT=行格式名称 ALTER TABLE 表名 ROW_FORMAT=行格式名称
2. 行格式
上图就是Compact行格式示意图,大体可分为记录的额外信息和记录的真实数据接下来我们一个个分析:
1. 变长字段长度列表
变长字段长度列表是Mysql真实存储变长字段实际长度并且按逆序存放,如VARCHAR(N)、VARBINARY(M) 、各种 TEXT 类型,各种 BLOB 类型,并且变长字段长度列表只存储非NULL字段,值为NULL不存储
假设有一张表student表,字段name是VARCHAR(10)和sex是CHAR(1),已知字段name为小李的占用4个字节,字段sex为男的占用1个字节,那么变长列表就为:
2. NULL值列表
这个列表主要是Mysql为了节省空间,避免大量NULL值占用不必要空间。 NULL值列表存储规则:
- 允许存储 NULL 的列
- 存储列按二进制逆序规则,1代表为NULL,0代表非NULL
上图有两条记录,其中第二条name有值,sex为NULL,那么它的NULL值列表如下图:
因为一个字节占八个比特位,缺失部分需要补零填充,所以填充后Compact行格式为:
3. 记录头信息
记录头信息 ,它是由固定的 5 个字节组 成。 5 个字节也就是 40 个二进制位,不同的位代表不同的意思,如图:
相关介绍如下,大家混个脸熟,后面文章会针对相关名称进行重点描述:
4. 记录的真实数据
记录的真实数据除了对应的字段还有DB_ROW_ID、DB_TRX_ID、DB_ROLL_PTR这三个隐藏列,DB_ROW_ID也就是我们常说的行ID,在设计表阶段没有自定义自增主键或Unique键,MYSQL会自动给表添加一个自增的行id作为主键,INNODB会为每条记录都添加DB_TRX_ID(事务ID)和DB_ROLL_PTR(回滚指针)这两个在后续undolog和MVCC中会重点介绍。
你学废了吗?:)
二、行溢出
1. 为什么会行溢出
我们知道VARCHAR类型最多可以占用65535个字节,而数据页默认为16KB,也就是16384(16 * 1024)个字节,而且MYSQL规定一页数据页最少占用两行,也就是一行数据最大存放8192(16384/2)个字节,那么既然存储数据大于MYSQL规定一行占用最大值,改怎么办呢?很简单,行溢出。
在 Compact 和 Reduntant 行格式中,对于占用存储空间非常大的列,在 记录的真实数据 处只会存储该列的一部分数据(768字节),把剩余的数据分散存储在几个其他的页中,然后 记录的真实数据处用20个字节存储指向这些页的地址 (当然这20个字节中还包括这些分散在其他页面中的数据的占用的字节数),从而可以找到剩余数据所在的页。
2. 行溢出临界点
每个页除了存放我们的记录以外,也需要存储一些额外的信息,乱七八糟的额外信息加起来需要 136 个字节 的空间(现在只要知道这个数字就好了),其他的空间都可以被用来存储记录。
刚刚我们提到MYSQL规定一页数据页至少存两行数据,也就是一行至少存8192个字节。Innodb存储引擎中除了存储记录以外还要额外每行存储27个字节其他信息。 这27个字节包括下边这些部分:
- 2个字节用于存储真实数据的长度
- 1个字节用于存储列是否是NULL值
- 5个字节大小的头信息
- 6个字节的 row_id 列
- 6个字节的 transaction_id 列
- 7个字节的 roll_pointer 列
很熟悉吧,都是上面我们介绍的。 所以添加每行数据只要满足如下公式,就会发生行溢出: 136 + 2 *(27+n)> 16384 其中n代表每行存储数据,换算一下也就是 n > 8098 就会触发行溢出
说到这里,我们来思考两个问题。
- 第一个为什么Mysql规定至少要保证一页数据页两行数据?
- 第二个行溢出对我们设计表结构有影响吗?
第一个问题,我们可以从MYSQL存储结构B+树来分析,mysql想让一个数据页中能存放更多的数据行,至少也得要存放两行数据。否则就失去了b+tree的意义。b+tree也退化成一个低效的链表。
第二个问题,当然有影响行溢出是很浪费Mysql空间和性能的操作。想想有些老项目,一张表有几十甚至上百个字段,那是多么恐怖的事情,不好阅读是其次,一行数据肯定会触发行溢出,使B+树层高提升了增加查询时长。所以我们设计表时候对每个字段类型和长度都要根据自己实际业务合理设计。
三、其他行格式
上文我介绍了Compact行格式的结构,其他三种格式**Redundant行格式是MYSQL 5.1 **及之前使用的格式,太老了不介绍(PS:现在没有那个公司会使用这个格式吧)剩下两个行格式, Dynamic 和 Compressed 行格式,我现在使用的 MySQL 版本是 5.7 ,它的默认行格 式就是 Dynamic ,这俩行格式和 Compact 行格式挺像,只不过在处理 行溢出 数据时有点儿分歧,它们不会在记录的真实数据处存储字段真实数据的前 768 个字节,而是把所有的字节都存储到其他页面中,只在记录的真实数据处存储其他页面的地址,就像这样:
Compressed 行格式和 Dynamic 不同的一点是, Compressed 行格式会采用压缩算法对页面进行压缩,以节省空间。
四、小结
- MYSQL中InnoDB存储引擎一共有四种存储格式:Compact、Redundan、Dynamic、Compressed 。
- 数据页是磁盘和内存交互最小单位。
- 一页数据默认为16KB,当一行数据太大就会把多余数据存储到其他数据页中,这叫行溢出。
参考:
《MYSQL是怎样运行的》
《mysql实战45讲》
《MySQL技术内幕-InnoDB存储引擎的》的4.4章节 innodb行记录格式