物理数据存储格式

行格式

什么是行格式

我们插入的一条记录在磁盘中的存放方式称为行格式(也叫记录格式),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预留位
1delete_mask删除标志位
1min_rec_maskB+树的每一层非叶子节点的最小值会有这个标志
4n_owned拥有的记录数
13heap_no当前记录在记录堆的位置信息
3record_type当前记录的类型,0:普通;1:B+树非叶子节点;2:最小值数据;3:最大值数据
16next_record下一条数据的指针

隐藏字段

  • DB_ROW_ID:行唯一标识,在没有指定主键和Unique key唯一索引的时候,会以他作为主键
  • DB_TRX_ID:事务相关
  • DB_ROLL_PTR:回滚指针,用于事务回滚

示例

红色的列表示不允许为空
varchar(10)varchar(20)varchar(5)char(2)char(3)可能格式
helloniceazxcc0x01,0x04,0x05[头字段]hello nice a zx cc
pptwordflashdz0x05,0x04,0x03[头字段]ppt wprd flash d z
jackNULLccpsNULL0x02,0x04[头字段]jack cc ps
tom3mgNULLKG0x02,0x01,0x03[头字段]tom 3 mg KG

紧凑的意义

节省空间?

读取的过程

  1. 示例样本(选自上方)
    0x02,0x01,0x03[010000001000011111]tom 3 mg KG
  2. 先读取出变长字段长度列表和NULL值列表,分析得到几个变长字段以及哪几个字段是NULL。因为MYSQL自己定义的列以及类型自己最清楚哪些列是变长哪些列允许NULL
  3. 第一个字段不允许为空所以不会出现在NULL值列表中,是变长类型所以从变长列表中取出0x03,就去字段列表中读取3个字符的长度,得到tom
  4. 第二个字段为变长允许为空,所以读取NULL值列表知道不为空,在读取变长列表得到长度为0x01,所以读取1个字符的长度得到3
  5. 第三个字段为变长允许为空,所以读取NULL值列表知道不为空,在读取变长列表得到长度为0x02,所以读取2个字符的长度得到mg
  6. 第四个字段为定长允许为空,所以直接读取NULL值列表知道为空,所以直接为null
  7. 第五个字段为定长允许为空,直接读取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
  • 未知

一个口述的数据插入流程

  1. 根据表名找到对应的表空间,定位到对应的磁盘文件
  2. 从磁盘文件中拿到一个extent组,从里面找出一页数据页
  3. 加载数据页到Buffer Pool
最后修改:2024 年 01 月 11 日
如果觉得我的文章对你有用,请点个赞吧~