前言
? 关于数据库我们知道是通过内存对磁盘进行操作的,也知道数据会落实到磁盘上,但是数据在磁盘上的存储结构可能大家还不是很清楚。
? MySQL服务器上负责对表中的数据的读取和写入的工作的部分是存储引擎,而关于服务器会支持不同类型的服务器,如:InnoDB、MyISAM、Memory......
? 不同的存储引擎都是为了实现不同的特性进行开发的,真实数据的存储在不同的存储引擎中存放的格式一般是不同的,有的存储引擎比如Memory都不用磁盘来存储数据,就跟NoSQL一样,服务器关闭后数据就不见了。InnoDB是MySQL的默认储存引擎,也是我们大家常用的存储引擎。
? Mysql把页作为管理存储空间的基本单位,一个页的大小一般是16KB,大家知道记录其实是被储存在页中的,本文将详细的带大家看一下InnoDB储存引擎中页的结构。
引用
? 参考文章:InnoDB数据页结构
InnoDB页
简介
? InnoDB
是一个将表中的数据存储到磁盘上的存储引擎,所以即使关机后重启我们的数据还是存在的。而真正处理数据的过程是发生在内存中的,所以需要把磁盘中的数据加载到内存中,如果是处理写入或修改请求的话,还需要把内存中的内容刷新到磁盘上。而我们知道读写磁盘的速度非常慢,和内存读写之间的差距就不再多说,所以当我们想从表中获取某些记录时,InnoDB
存储引擎需要一条一条的把记录从磁盘上读出来么?不,那样会慢死,InnoDB
采取的方式是:将数据划分为若干个页,以页作为磁盘和内存之间交互的基本单位,InnoDB中页的大小一般为 16KB。也就是在一般情况下,一次最少从磁盘中读取16KB的内容到内存中,一次最少把内存中的16KB内容刷新到磁盘中。
页结构
? 页的本质介绍一个大小为16KB大小的存储空间,页有很多种类型的,不同的类型有不同的作用;
? 用于存储记录的页被称为数据页 ,大小也为16KB,但是这16KB大小的存储空间被划分为多个部分,不同的部分当然有着不同的功能,结构如下:
? 从上面的图可以看到,InnoDB的页结构分为七个部分,下面用表格说明一下各个部分对应的作用:
名称 | 中文名 | 占用空间大小 | 简单描述 |
---|---|---|---|
File Header | 文件头 | 38字节 | 描述页的信息 |
Page Header | 页头 | 56字节 | 页的状态信息 |
Infimum + SupreMum | 最小记录和最大记录 | 26字节 | 两个虚拟的行记录(后面会说明) |
User Records | 用户记录 | 不确定 | 实际存储的行记录内容 |
Free Space | 空闲空间 | 不确定 | 页中尚未使用的空间 |
Page Directory | 页目录 | 不确定 | 页中的记录相对位置 |
File Trailer | 文件结尾 | 8字节 | 结尾信息 |
? 下面会详细介绍他们的作用
页中的存储
? 当我们在存储数据的时候,记录会存储到User Records部分 。但是在一个页新形成的时候是不存在User Records
这个部分的,每当我们在插入一条记录的时候,都会从Free Space中去申请一块大小符合该记录大小的空间并划分到User Records
,当Free Space
的部分空间全部被User Records
部分替换掉之后,就意味着当前页使用完毕,如果还有新的记录插入,需要再去申请新的页,过程如下:
记录头
? 对于User Records中的每一条记录的管理,MySQL做了很多的处理,究竟做出了什么处理呢,这需要从每条记录里面的记录的额外信息
部分中的记录头信息说起
这是有关行格式的知识,关于行格式(指的就是一条记录的存储结构,有多种格式),有兴趣的可以去看一下InnoDB记录存储结构 这篇文章。
? 首先,创建一个表:
mysql> CREATE TABLE page_demo(
-> c1 INT,
-> c2 INT,
-> c3 VARCHAR(10000),
-> PRIMARY KEY (c1)
-> ) CHARSET=ascii ROW_FORMAT=Compact;
Query OK, 0 rows affected (0.03 sec)
mysql>
? 如上所示,表中有三列,c1和c2用来存储整数的,c3用来存储字符串的。因为指定了主键为c1,所以MySQL就不会去创建那个隐藏的 row_id 列。指定了ascii
字符集以及Compact
的行格式,所以里面的每一条记录的行格式如下:
? 先看一下行格式中每个属性代表的意思:
?
名称 | 大小(单位:bit) | 描述 |
---|---|---|
预留位1 | 1 | 没有使用 |
预留位2 | 1 | 没有使用 |
delete_mask | 1 | 标记该记录是否被删除 |
min_rec_mask | 1 | 标记该记录是否为B+树的非叶子节点中的最小记录(索引时用到) |
n_owned | 4 | 表示当前槽管理的记录数 |
heap_no | 13 | 表示当前记录在记录堆的位置信息 |
record_type | 3 | 表示当前记录的类型,0 表示普通记录,1 表示B+树非叶节点记录,2 表示最小记录,3 表示最大记录 |
next_record | 16 | 表示下一条记录的相对位置 |
? 由于这里只是描述在User Records
中记录头的作用,所以下面只会说明一些相关的属性以及c1
、c2
、c3
列的信息(其他信息没画不代表它们不存在,只是为了理解上的方便省略了~),简化后的行格式示意图就是这样:
? 我们往表中插入几条数据:
mysql> INSERT INTO page_demo VALUES(1, 100, 'aaaa'), (2, 200, 'bbbb'), (3, 300, 'cccc'), (4, 400, 'dddd');
Query OK, 4 rows affected (0.00 sec)
Records: 4 Duplicates: 0 Warnings: 0
mysql>
? 下面看看几条记录在页中的User Records
是以何种形式进行体现的,为了方便理解,下面的图中把记录中的头信息和实际的数据都用的十进制进行的表示(其实都是二进制):
? 下面说说,记录头中的各个部分代表的含义:
delete_mask
? 这个属性说的是当前这条记录是否被删除,当值为0的时候代表着没有被删除,为1的时候标志着被删除了。
是的,您没看错,当您执行删除一个记录的操作的时候,被删除的记录还存在页中,您对它进行了删除,它会把的
记录头中的这个属性设置为1,只是打了个标记。
原因
这些被删除的记录之所以不立即从磁盘上移除,是因为移除它们之后把其他的记录在磁盘上重新排列需要性能消耗,所以只是打个删除标记而已,而且这部分存储空间之后还可以重用,也就是说之后如果有新记录插入到表中的话,可能把这些被删除的记录占用的存储空间覆盖掉。
如果您想彻底的从磁盘上移除这些被删除的记录,可以使用这个语句:
optimize table '表名';
执行这个命令后服务器会重新规划表中记录的存储方式,把被标记为删除的记录从磁盘上移除。
min_rec_mask
? 有关索引的,暂时不说,后面说到索引会说明;
n_owned
? 下面会讲
heap_no
? 这个属性是表示的当前记录在当前页中的位置,上面的一张图如果您仔细看了的话,会发现它们的位置分别是2、3、4、5,那么问题