MySQL高级之InnoDB的逻辑存储结构

610 阅读8分钟

在InnoDB存储引擎模式下创建的表对应的表结构&表数据都存储在哪里呢?我相信你也和我有同样的疑问,那么本文我们就来谈谈MySQL表在文件系统中到底是如何表示的。

InnoDB逻辑存储结构

image-20230217142245101.png

从InnoDB逻辑存储结构中可以看出,所有数据都被逻辑的存放在一个空间中,这个空间就叫做表空间【Tablespace】,而表空间又由段【Segment】、区【Extent】、页【Page】、行【Row】组成。

表空间(Tablespace)

在InnoDB存储引擎中数据是按照表空间来组织存储的,即所有数据都存放在表空间中。表空间是InnoDB在磁盘中的部分,分为系统表空间(System Tablespace,又称共享表空间)、独立表空间(File-Per-Table Tablespaces)、撤销表空间(Undo Tablespaces)、通用表空间(General Tablespaces)、临时表空间(Temporary Tablespaces)

1)系统表空间

注意:MySQL5.6.6以及之后的版本中,InnoDB并不会默认的把各个表的数据存储到系统表空间中,而是每一个表都对应一个我们即将讲解的独立表空间。

整个MySQL进程只有一个系统表空间。默认情况下,InnoDB会在数据目录下创建一个名为**ibdata1、大小为12M的文件,这个文件就是对应的系统表空间在文件系统上的表示。怎么才12M?注意这个文件是自扩展文件** ,当不够用的时候它会自己增加文件大小。

mysql> show variables like '%innodb_data_file_path%';
+-----------------------+------------------------+
| Variable_name         | Value                  |
+-----------------------+------------------------+
| innodb_data_file_path | ibdata1:12M:autoextend |
+-----------------------+------------------------+

当然,如果想修改系统表空间的大小或名称可在my.cnf配置文件中进行修改,如下所示

[server]
innodb_data_file_path=data1:512M;data2:512M:autoextend

那么系统表空间中到底包含哪些内容呢?

  • 省流:系统表空间包含数据字典,双写缓冲,变更缓冲区、undo日志,以及在系统表空间创建的表的数据和索引。

毫无疑问的是肯定包含我们插入的数据,那么除了这些数据之外还需要保存许多额外的信息,比如:

- 某个表属于哪个表空间,表里面有多少列
- 表对应的每一个列的类型是什么
- 该表有多少索引,每个索引对应哪几个字段,该索引对应的根页面在哪个表空间的哪个页面
- 该表有哪些外键,外键对应哪个表的哪些列
- 某个表空间对应文件系统上文件路径是什么
- ....

这些数据是为了更好的管理我们插入的数据,因此这些数据也称之为元数据,而这些元数据就存储在InnoDB定义的内部系统表中,这些系统表就称之为数据字典。

InnoDB有四个最基本的系统表(当然除了这四个还有其它的一些系统表),用来存储用户定义的表信息,列信息,索引信息及索引列信息等,这些表分别为SYS_TABLESSYS_COLUMNSSYS_INDEXESSYS_FIELDS

  • SYS_TABLES记录了:表的名称、InnoDB存储引擎中每个表对应唯一的ID、表拥有列的个数、表的类型(包括一些文件格式,行格式等)、MIX_ID[已过时]、表所属表空间的ID等
  • SYS_COLUMNS记录了:列所属表对应ID、列是表中的第几列、列名称、主数据类型(INT,CHAR..)、精确数据类型(比如是否允许NULL等)、列最多占用存储空间的字节数、列的精度【默认都是0】
  • SYS_INDEXES记录了:索引所属表对应的ID、索引的唯一ID、索引名称、索引包含列的个数、索引类型、索引根页面所在表空间ID、索引根页面所在的页面号、记录了页面中记录被删除到某个比例,就把该页面与相邻页面合并的比例
  • SYS_FIELDS记录了:索引列所属索引的ID、索引列在某个索引中是第几列、索引列的名称

2)独立表空间

上面提到在MySQL5.6.6以及之后的版本中,InnoDB并不会默认的把各个表的数据存储到系统表空间中,而是为每一个表建立一个独立表空间,也就是说我们创建了多少个表,就有多少个独立表空间。使用独立表空间来存储表数据的话,会在该表所属数据库对应的子目录下创建一个表示该独立表空间的文件,文件名和表名相同,只不过添加了一个 .ibd的扩展名而已,该文件就用来存储对应表的数据以及索引

mysql> show variables like 'innodb_file_per_table';
+-----------------------+-------+
| Variable_name         | Value |
+-----------------------+-------+
| innodb_file_per_table | ON    |
+-----------------------+-------+
1 row in set (0.01 sec)

ON就表明每张表对应一个.ibd文件,当然我们也可以在my.cnf配置文件中手动修改

[server]
innodb_file_per_table=0  # 0:代表使用系统表空间; 1:代表使用独立表空间

独立表空间由段、区、页组成,一个表空间划分为多个区(extent),一个区内包含物理上连续的64个页,因此一个区空间大小为 64 * 16KB = 1M。到这里有人问了,段呢?

  • 段不对应表空间的某一连续物理区域,而是一个逻辑概念,它是由表空间这个物理空间中的若干个零散页和一系列完整的区组成。

3)其它表空间

撤销表空间(Undo Tablespaces):该表空间专门用来存放undolog的日志文件。

临时表空间(Temporary Tablespaces):用于存放用户创建的临时表和磁盘内部临时表。

通用表空间(General Tablespaces):类似于系统表空间,通用表空间是共享表空间,可以存储多个表的数据。可使用CREATE TABLESPACE进行创建。

页(Page)

1)介绍

  • InnoDB将数据划分为若干个页,InnoDB中页的大小默认为16KB
  • 页作为磁盘和内存之间交互的基本单位(注意和操作系统操作的页不同),也就是一次最少从磁盘中读取16KB的内容到内存中,一次最少把内存中16KB内容刷新到磁盘中。
  • 在数据库中,不论读一行,还是读多行,都是将这些行所在的页进行加载,即数据库I/O操作的最小单位是页
  • 页与页之间通过双向链表连接,一个页中存储多个行记录,记录与记录之间通过单向链表连接。
  • 页按照类型划分的话,常见的有数据页、系统页、Undo页、事务数据页等,其中数据页是我们最常使用的页。

2)数据页结构

image-20230217190342581.png

  • File Header:文件头,描述页的信息
  • Page Header:页头,页的状态信息
  • Infimum + Supremum:最大和最小记录,这是两个虚拟的行记录
  • User Records:用户记录,存储行记录内容
  • Free Space:空闲记录,页中还没有被使用的空间
  • Page Directory:页目录,存储用户记录的相对位置
  • File Tailer:文件尾,校验页是否完整

关于页的具体内容,后续写好会贴在这里。

区(Extent)

1)为什么要有区

上面提到页与页之间通过双向链表进行连接,若页与页间隔太远的话,而随机I/O【定义:读写操作时间连续,但访问地址不连续,随机分布在磁盘的地址空间中】是非常慢的。因此为了尽量让链表上相邻的页物理上也相邻,引入了区。

  • 一个区在物理位置上就是连续的64个页。
  • 数据量大的时候,可一次性分配多个区,不必再按照页为单位进行分配了。
  • 消除很多的随机I/O。

2)碎片区

对于数据量太小的表不足以填满整个区的情况,造成空间的浪费。因此针对这种情况,InnoDB提出了碎片区的概念。

碎片区中的页并不都是为了存储同一个段而存在的,比如有的页用于段A,有的用于段B,有的页甚至哪个段也不属于。碎片区直属于表空间,并不属于任何一个段。

综上,为段分配的策略是:

  • 起初向表中插入数据的时候,段是从某个碎片区以单个页面来分配存储空间的。
  • 当某个段已经占用了**32个碎片区**页面之后,就会申请以完整的区为单位来分配存储空间。

这时候再体会一下段是若干个零散页一系列完整的区的集合。

段(Segment)

1)为什么要有段

再次声明:段不对应表空间的某一连续物理区域,而是一个逻辑概念,它是由表空间这个物理空间中的若干个零散页一系列完整的区组成。

对于范围查询,其实是对B+树叶子节点中的记录进行顺序扫描,如果不区分叶子节点和非叶子节点,统统把节点代表的页面放到区中,进行范围扫描的效果相对差很多。因此InnoDB对B+树的叶子节点非叶子节点进行了区分,将叶子节点和非叶子节点分别放到各自的区中。(即1个索引会生成2个段)

  • 存放叶子节点的区的集合称为叶子节点段。
  • 存放非叶子节点的区的集合称为非叶子节点段。

2)分类

  • 回滚段:为存储一些特殊的数据而定义的段
  • 数据段:B+树的叶子节点
  • 索引段:B+树的非叶子节点

在InnoDB存储引擎中,对于段的管理都由引擎自身完成。