目录
[1. 什么是表空间文件](#1. 什么是表空间文件)
[2. 表空间与表空间文件的关系是啥](#2. 表空间与表空间文件的关系是啥)
[3. 用户数据在表空间中是怎么存储的](#3. 用户数据在表空间中是怎么存储的)
[4. 段, 区组, 区, 页, 数据行是如何管理起来的](#4. 段, 区组, 区, 页, 数据行是如何管理起来的)
[4.1 页](#4.1 页)
[4.2 区](#4.2 区)
[4.3 区组](#4.3 区组)
[4.4 段](#4.4 段)
[4.5 数据行](#4.5 数据行)
MySQL 为了有效维护数据而定义的一系列 数据结构
1. 什么是表空间文件
表空间文件是用来存储表中数据的文件, 表空间文件的大小由储存的数据多少决定, 不同的表空间文件存储的种类也有所不同, 在 MySQL中表空间分为五类 : 系统表空间, 独立表空间, 通用表空间, 临时表空间和撤销表空间
2. 表空间与表空间文件的关系是啥
表空间可以理解为 MySQL 为了管理数据而设计的一种数据结构, 主要描述的对结构的定义, 表空间是对定义的具体实现, 以文件的形式存在于磁盘上 (就可以理解为小区在开发之前都要有的设计图) (设计层面)
表空间文件接可以理解为 (根据设计图进行施工, 最终称为我们可以使用的小区) (实体层面)
3. 用户数据在表空间中是怎么存储的

可以这么理解, 多个数据行组成了页, 多个页组成了区, 多个区组成了区组, 多个区组组成了段, 多个段组成了表空间
4. 段, 区组, 区, 页, 数据行是如何管理起来的

4.1 页
页是 InnoDB 磁盘管理的最小单位, 是 MySQL 应用层的一个概念, 是 MySQL 根据自身的应用场景, 定义的一种数据结构
通常操作系统中的文件系统在管理磁盘文件时以 4 KB 大小为一个管理单元, 称为 "数据块", 但是在数据库的应用场景里, 查询时数据量都比较大, 如果也是用 4 KB 做数据存储的最小单元, 就是明显有点小了, 同时会造成频繁的磁盘 I/O, 导致降低效率
所以 MySQL 根据自身情况定义了大小为 16 KB 的页, 作为磁盘管理的最小单位
但会不会读的数据过多而导致资源的浪费呢?
不会的, 每次内存与磁盘的交互至少读取一页,所以在磁盘中每个页内部的地址都是连续的,之所以这样做,是因为在使用数据的过程中,根据局部性原理,将来要使用的数据大概率与当前访问的数据在空间上是临近的,所以一次从磁盘中读取一页的数据放入内存中,当下次查询的数据还在这个页中时就可以从内存中直接读取,从而减少磁盘I/O,提高性能
页的组成 : 页头, 页尾, 页主体

页头 :


页尾 :

LSN : 表示日志序号。用一个任意的、不断增加的值表示日志中记录的操作对应的时间点,用8字节的无符号长整形表示
数据行 :

-
页是 InnoDB 中磁盘管理的最小单位
-
页是磁盘与内存交互的最小单位
-
每次进行磁盘IO读取最小一页的数据
4.InnoDB 中使用的 B+ 数索引也是基于页实现的
数据页之间如何进行关联 : (通过双向链表连接每个页)


4.2 区
我们知道, 不同的页在磁盘中的存储大概率是不连续的, 这个时候我们要是查找数据大于一页的会造成磁盘的随机访问, 这中途会有磁盘的物理转动, 很影响效能

MySQL使用Extent(区)这个结构来管理页, 规定每个区固定大小为 1MB 可以存放 64 个页,这时如果跨页读数据时,大概率都在附近的地址,可以大幅减少碰头移动, 每两个区也是通过双向链表连接起来的
同时,如果频繁的读取某个区中的页,可以把整个区都读取出来放入内存中,减少后续查询对磁盘的访问次数,进一步提升效率

那么又有一个问题来了,新创建表时没有数据,或者说有的表只有很少的数据,1MB的空间用不完,那不是就存在空间浪费的问题吗?
为了节省空间,最初只创建7个初始页(在MySQL5.7中创建6个初始页),而不是一个完整的区
这些零散页会放在表空间中一个叫碎片区的区域,随着数据量的增加,会申请新的页来存储数据,当碎片区达到32个页的时候,后续每次都会申请一个完整的区来存储更多的数据;

4.3 区组
当表中的数据越来越多,为了有效的管理区,定义了区组的结构,每个区组固定管理256个区即256MB,通过区组可以在物理结构层面非常高效的管理和定位到每个区, 每两个区组也是通过双向链表组织起来的


4.4 段
"段"并不对应表空间中的连续的物理区域,可以看做是"区"和"页"的一个附加标注信息段的主要作用是区分不同功能的区和在碎片区中的页,主要分为"叶子节点段"和"非叶子节点段"等,这两个段和我们常说的 B+ 树索引中的叶子、非叶子节点对应,可以简单的理解为"非叶子节点段"存储和管理索引树,"叶子节点段"存储和管理实际数据,从逻辑上讲,最终由"叶子节点段"和"非叶子节点段"等段构成了表空间 .ibd 文件

总的来说, 当我们创建一个表的时候, InnoDB引擎会按照索引信息自动帮我们创建索引页和真实数据页, 其中索引页就一步一步组成非叶子节点段, 真实数据页就一步一步组成叶子节点段


4.5 数据行
数据行时如何组织数据的 :
数据行通过下一行的地址偏移量,即next_record将页内所有数据行组成了一个单向链表,这里要注意的是,地址偏移量指向的是下一行中真实数据的起始地址,这样做的好处是,向右是真实数据,向左就是头信息,而无需额外的长度计算

由于每个数据行的长度不同, 所以每个数据页中的数据行数页不同, 那么我们该怎么标识新页中的第一行和最后一行
了解了行的基本结构和组织方式之后,那么当遍历页中的行时,从哪里开始到哪里结束呢?为了解决这个问题,每当创建一个新页,都会自动分配两个行,一个是行类型为2的最小行 Infimun ,heap_no 位置固定为 0 号,和一个是行类型为3的最大行 Supremun,heap_no 位置固定为1号,这两个行并不存储任何真实信息,而是做为数据行链表的头和尾,虽然不存储真实数据,但它们的数据结构和真实数据行完全一致,只不过数据区域存储的是代表它们身份的固定字符串 Infimun 和Supremun,新页中没有数据时,最小行Infimun的next_record直接连接最大行 Supremun,最大行不连接任何行,它的 next_record 为0

当向一个新页中插入数据时, 新的数据行插入的位置在最小行和最大行之间
当向一个新页插入数据时,heap_no 会从 2 号开始递增,表示当前记录在页面堆中的相对位置;如果是真实数据则record_type为 0,如果是索引目录(B+树非叶节点)数据则 record_type 为 1;再将 Infimun 连接第一个数据行,最后一行真实数据行连接 Supremun ,这样数据行就构建成了一个单向链表,更多的行数据插入后,会按照主键从小到大的顺序进行链接;为了使页的结构更加清晰,通常将页中有数据行的区域称为用户数据区 UserRecords ,把未被数据行占用的区域称为空闲区 FreeSpace ,如下图所示:

如何定位数据行在页中的位置 ? (页目录)
为了提高查询效率,InnoDB采用二分查找来解决查询效率问题。
具体实现方式是,在每一个页中加入一个叫做页目录PageDirectory的结构,将页内包括头行、尾行在内的所有行进行分组,约定头行单独为一组,其他每个组最多8条数据,同时把每个组最后一行在页中的地址,按主键从小到大的顺序记录在页目录中在,页目录中的每一个位置称为一个槽,每个槽都对应了一个分组,这样在插入数据行完成链接后,一旦最后一个分组中的数据行超过分组的上限8个时,就会分裂出一个新的分组,为了快速判断每个分组是否达到了8个的上限,在每个分组最后一行中用n_owned记录了这个分组内的行数,与此同时在页目录中创建一个新的槽,后续插入的行都遵守这个规则;
后续在查询某行时,就可以通过二分查找,先找到对应的槽,然后在槽内最多8个数据行中进行遍历即可,从而大幅提高了查询效率;
例如要查找主键为6的行,先比对槽中记录的主键值,定位到最后一个槽2,再从最后一个槽中的第一条记录遍历,第二条记录就是我们要查询的目标行。

