表、行和字段是逻辑上的概念
而表空间、数据区和数据页其实已经落实到物理上的概念

实际上表空间、数据页这些东西,都对应到了MySQL在磁盘上的一些物理文件
**
你一个SQL语句仅仅指定了你要查询或者更新哪个表的哪些数据,那你怎么知道这些
数据在哪个表空间里?在哪个数据区里?在哪些数据页里?对应是在MySQL机器上的哪些磁盘文件里呢

为什么不能直接更新磁盘上的数据

为什么MySQL要设计这么一套复杂的数据存取机制,要基于内存、日志、磁盘上的数据文件来完成数据的读写呢?为什么对insert、update请求,不直接更新磁盘文件里的数据呢

因为来一个请求就直接对磁盘文件进行随机读写,然后更新磁盘文件里的数据,虽然技术上是可以做到的,但是那必然导致执行请求的性能极差.

所以MySQL才设计了如此复杂的一套机制,通过内存里更新数据,然后写redo log以及事务提交,后台线程不定时刷新内存里的数据到磁盘文件里

MySQL为什么要引入数据页这个概念

如果每次都是一条数据一条数据的加载到内存,效率不高
所以innodb存储引擎在这里引入了一个数据页的概念,也就是把数据组织成一页一页的概念,每一页有16kb,然后每次加载磁盘的数据到内存里的时候,是至少加载一页数据进去,甚至是多页数据进去
image.png

一行数据在磁盘上是如何存储的

其实这里涉及到一个概念,就是行格式。我们可以对一个表指定他的行存储的格式是什么样的,比如我们这里用一个COMPACT格式
CREATE TABLE table_name (columns) ROW_FORMAT=COMPACT
ALTER TABLE table_name ROW_FORMAT=COMPACT
大概格式类似下面这样:
变长字段的长度列表,null值列表,数据头,column01的值,column02的值,column0n的值……

变长字段在磁盘中是怎么存储

必须在他开头的变长字段长度列表中存储几个变长字段的长度,一定要注意一点,他这里是逆序存储的

比如一行数据有VARCHAR(10) VARCHAR(5) VARCHAR(20) CHAR(1) CHAR(1),一共5个字段,其中三个是变长字
段,此时假设一行数据是这样的:hello hi hao a a

现在hello hi hao三个字段的长度分别是0x05 0x02 0x03,但是实际存放在变长字段长度列表的时候,是逆序放的,所以一行数据实际存储可能是下面这样的:
0x03 0x02 0x05 null值列表 头字段 hello hi hao a a

NULL值的存储

NULL值不能直接存储。一行数据里的NULL值是肯定不会直接按照字符串的方式存放在磁盘上浪费空间的
NULL值是以二进制bit位来存储的
CREATE TABLE customer (
name VARCHAR(10) NOT NULL,
address VARCHAR(20),
gender CHAR(1), 定长字段
job VARCHAR(30),
school VARCHAR(50)
) ROW_FORMAT=COMPACT;
其中有4个变长字段,还有一个定长字段,然后第一个name字段是声明了NOT NULL的,就是不能为NULL,其他4个字段都可能是NULL的
假设表里面有5个字段,分别为name、address、genderjob、school,就代表了客户的姓名、地址、性别、工作以及学习。
“jack NULL m NULL xx_school”,他的5个字段里有两个字段都是NULL,这个怎么存储呢?
null值列表存储规则 :这个NULL值列表是这样存放的,你所有允许值为NULL的字段,注意,是允许值为NULL。如果bit值是1说明是NULL,如果bit值是0说明不是NULL。但是实际放在NULL值列表的时候,他是按逆序放的。
所以 “jack NULL m NULL xx_school” —》1010 jack是非null字段,不参与 —》经过逆序 0101

整体这一行数据看着是下面这样的
0x09 0x04 0101 头信息 column1=value1 column2=value2 … columnN=valueN
NULL值列表长度是 8的倍数,所以
0x09 0x04 00000101 头信息 column1=value1 column2=value2 … columnN=valueN

数据头

40个bit位的数据头
第一个bit位和第二个bit位,都是预留位,是没任何含义的。
有一个bit位是delete_mask,他标识的是这行数据是否被删除了
一个bit位是min_rec_mask,在B+树里每一层的非叶子节点里的最小值都有这个标记
有4个bit位是n_owned,
13个bit位是heap_no,他代表的是当前这行数据在记录堆里的位置
3个bit位的record_type,这就是说这行数据的类型:
0代表的是普通类型,1代表的是B+树非叶子节点,2代表的是最小值数据,3代表的是最大值数据
16个bit的next_record,这个是指向他下一条数据的指针

实际数据在磁盘上是如何存储

首先有一个DB_ROW_ID字段,这就是一个行的唯一标识,是他数据库内部给你搞的一个标识,不是你的主键ID字段。如果我们没有指定主键和unique key唯一索引的时候,他就内部自动加一个ROW_ID作为主键。
接着是一个DB_TRX_ID字段,这是跟事务相关的,他是说这是哪个事务更新的数据,这是事务ID
最后是DB_ROLL_PTR字段,这是回滚指针,是用来进行事务回滚的

0x09 0x04 00000101 0000000000000000000010000000000000011001 00000000094C(DB_ROW_ID)
00000000032D(DB_TRX_ID) EA000010078E(DB_ROL_PTR) 616161 636320 6262626262

行溢出

比如有一个表的字段类型是VARCHAR(65532),意思就是最大可以包含65532个字符,那也就是65532个字节,这就远大于16kb的大小了,也就是说这一行数据的这个字段都远超一个数据页的大小了
一行数据存储的内容太多了,一个数据页都放不下了,此时只能溢出这个数据页,把数据溢出存放到其他数据页里去,那些数据页就叫做溢出页
**

数据页到底长个什么样子

数据页拆分成了很多个部分:文件头、数据页头、最小记录和最大记录、多个数据行、空闲空间、数据页目录、文件尾部
其中文件头占据了38个字节,数据页头占据了56个字节,最大记录和最小记录占据了26个字节,数据行区域的大小是不固定的,空闲区域的大小也是不固定的,数据页目录的大小也是不固定的,然后文件尾部占据8个字节

表空间

表空间就是对应一些磁盘上的数据文件。我们自己创建的表对应的表空间可能就是对应了一个“表名.ibd”数据文件

数据区

一个表空间里包含的数据页(一个16k)实在是太多了,不便于管理,所以在表空间里又引入了一个数据区的概念,英文就是extent
一个数据区对应着连续的64个数据页,每个数据页是16kb,所以一个数据区是1mb,然后256个数据区被划分为了一组。

当我们需要执行crud操作的时候,说白了,就是从磁盘上的表空间的数据文件里,去加载一些数据页出来到Buffer Pool的缓存页里去使用
image.png