MySQL是如何存储数据的

文章主要介绍 MySQL 的 InnoDB引擎是怎么存储数据的

InndoDB 逻辑存储结构

我们建一张 tb_user 表,就会生成一个名为 tb_user.ibd 的表空间文件
在这里插入图片描述
为了保证顺序IO,表空间被划分为多个连续的数据区,256个连续的数据区称为一个数据区组,一个数据区又由64个连续的数据页组成,数据页包含数据行。

一个数据页大小为 16 KB
一个数据区大小为 64 * 16 KB = 1 MB
一个数据区组大小为 256 * 1 MB = 256 MB

在 InnoDB 中有个参数 innodb_file_per_table ,默认值为 on,表示每张表的数据单独放到一个表空间。

常见的段有数据段,索引段,回滚段。数据即索引,那么数据段即为B+树的叶子节点,索引段即为B+树的非索引节点。
段是一个逻辑概念,段最小申请内存为1MB,为了防止空间浪费,先用32个页大小的碎片页来存放数据,在使用完这些页后才是64个连续页的申请。

页是 InnoDB 磁盘管理的最小单位,大小为16KB,和操作系统的页(4KB)概念不同。

表空间第一个数据区组的第一个数据区前3个数据页是固定的,存放一些描述信息
(1) FSP_HDR:存放表空间和这一组数据区的信息
(2) IBUF_BITMAP:这一组数据页的 insert buffer 数据信息
(3) INODE:存放特殊信息
表空间里的其他各组数据区,每一组数据区的第一个数据区的头两个数据页,都是存放特殊信息
(1) XDES:记录这一组数据区的信息
(2) IBUF_BITMAP:这一组数据页的 insert buffer 数据信息

InnoDB 数据页结构

在这里插入图片描述

File Header:文件头,记录页的一些头信息
Page Header:数据页头,记录数据页的状态信息
Supremum和Infimum:最大记录最小记录,限定数据记录的边界
User Records:数据行,实际存储行记录的内容
Free Space:空闲区域,数据页中剩余可插入数据的空间
Page Directory:数据页目录,存放数据记录的相对位置,有时候这些记录指针称为槽(Slots),我们通过B+树索引并不能找到具体的一条记录,只能找到记录所在的数据页,然后通过数据页目录进行二叉查找找到最终的数据
FileTrailer:文件尾部,校验页是否完整的写入磁盘

InnoDB 行记录格式

这里只介绍 compact 行记录格式
在这里插入图片描述
变长字段长度列表:字段类型为 varchar 的字段长度,按照列顺序逆序排列
null值列表:记录字段是否为null,0:非null,1:null,占用1个字节
数据头: 40位,固定占用5个字节
在这里插入图片描述
隐藏列:

  • DB_ROW_ID:如果 Innodb 没有指定主键,会生成一个rowid列,6字节
  • DB_TRX_ID:事务ID,6字节
  • DB_ROLL_PTR:回滚指针,7字节

在InnoDB中,每张表都有一个主键,如果再创建表时没有显示的定义主键,则InnoDB会按如下方式选择或创建主键:

  • 首先判断是否有非空的唯一索引,如果有,则该列即为主键
  • 如果不符合上述条件,InnoDB自动创建一个6字节大小的指针
    当有多个非空唯一索引时,InnoDB 存储引擎将选择建表时第一个定义的非空唯一索引为主键

行溢出

一个数据页大小为16KB,如果有一个表里有这样一个字段 varchar(65532) ,表示可以存放65532个字符,也就是65532个字节,远大于16KB,说明一个数据页放不下这一行数据。就需要将数据拆分到多个数据页中存放。每行数据都有个指针指向其他数据页中的溢出行。
在这里插入图片描述

如图所示,通过多个数据页来存储一行数据,当要读取这一行数据时,就需要加载多个数据页到缓冲池中了。

  • Compact格式与Redundant格式,在处理行溢出时,真实数据中只保存 768 字节的前缀,之后的数据都是偏移量,指向溢出页
  • Compressed格式与Dynamic格式,在处理行溢出时,数据完全溢出,在数据页只存放20字节的指针,实际的数据都存放在Off Page中

示例

下面通过一个示例说明:

我们创建一张 innodb 存储引擎的表,表结构如下:

create table tb_user (
      id varchar(8) not null,
      name  varchar(10),
      gender char(1),
      job  varchar(12),
      address  varchar(20)
);

往表中插入几行数据:

insert into tb_user values('1001','melon',null,'chengxuyuan',null);
insert into tb_user values('1002','zhangsan',null,null,'test');

通过 select * from tb_user 能够查询到这样一张视图
数据库数据:

idnameageaddressjob
1001melonNULLchengxuyuanNULL
1002zhangsanNULLNULLtest

使用16进制编辑器查看 tb_user.ibd 文件,高亮部分即为行存储的格式,我们看到2行数据是连续存储的,第二行数据紧挨着第一行数据

第一行数据高亮如下
在这里插入图片描述0b 05 04 :变长字段长度,逆序
0a :null值列表
00 00 10 00 30:数据头
00 00 00 00 29 73:RowId
00 00 00 00 3d a5:事务ID
c8 00 00 01 7b 01 10:回滚指针
31 30 30 31 1001
6d 65 6c 6f 6e melon
63 68 65 6e 67 78 75 79 75 61 6e chengxuyuan

1001字符串长度:4,用16进制表示:0x04
melon字符串长度:5,用16进制表示:0x05
chengxuyuan字符串长度:11,用16进制表示:0x0b
倒序展示为: 0b 05 04

允许为null值的有4个字段,2个null,2个非null,null用1表示
null值列表按顺序表示为:0101,倒序排列为:1010,
16进制表示为 0a

第二行数据高亮如下.
在这里插入图片描述
04 08 04 :变长字段长度,逆序
06 :null值列表
00 00 18 ff bf :数据头
00 00 00 00 29 74 :RowId
00 00 00 00 3d a6 :事务ID
c9 00 00 01 3f 01 10 :回滚指针
31 30 30 32 1002
7a 68 61 6e 67 73 61 6e zhangsan
74 65 73 74 test

1002字符串长度:4,用16进制表示:0x04
zhangsan 字符串长度:8,用16进制表示:0x08
test字符串长度:4,用16进制表示:0x04
倒序展示为: 04 08 04

允许为null值的有4个字段,2个null,2个非null,null用1表示
null值列表按顺序表示为:0110,倒序排列为:0110,
16进制表示为: 06

参考资料

《MySQL技术内幕 InnoDB存储引擎 第2版》

  • 5
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值