MySQL底层概述—4.InnoDB数据文件
大纲
1.表空间文件结构
(1)表空间Tablesapce
(2)段Segment
(3)区Extend
(4)页Page
(5)行Row
2.Page结构
(1)页结构各部分说明
(2)页结构整体划分
3.行记录格式
(1)行格式分类
(2)COMPACT行记录格式
(3)Compact中的行溢出机制
(4)其他行格式记录
1.表空间文件结构
(1)表空间Tablesapce
(2)段Segment
(3)区Extend
(4)页Page
(5)行Row
InnoDB表空间文件结构从逻辑上可以分为:
Tablespace(表空间)->Segment(段)->
Extent(区)->Page(页)->Row(行)
一个表空间会包含多个段,一个段会包含多个区(256个区就是一个组);一个区又会包含64个页,一个页里面又会包含一行一行的记录Row。
(1)表空间Tablesapce
一.表空间是什么
表空间能够看作是InnoDB存储引擎逻辑结构的最高层。表空间用于存储多个ibd数据文件,用于存储表的记录和索引。一个表空间文件可以包含多个段:叶子节点段、非叶子节点段、回滚段。
二.表空间类型
系统表空间、独占表空间、通用表空间、 临时表空间、Undo表空间。
(2)段Segment
一.段是什么
段是用来管理空间的申请以及将同类的区和页用链表管理起来。段是个逻辑概念,本质上是由若干个零散页面和若干个完整的区组成。段是为了保持叶子节点在磁盘上的连续,可以实现更好的顺序IO操作。
一个B+树索引被划分为两个段:一个叶子节点段和一个非叶子节点段。这样叶子节点就可以尽可能地存放在一起,非叶子节点也可以尽可能地存放在一起。
二.段的类型
常见的段有数据段、索引段、回滚段等。其中索引段就是非叶子节点部分,而数据段就是叶子节点部分,回滚段用于数据的回滚和多版本控制。
三.为什么引入段
原因一:
使用B+树执行查询时只是扫描叶子节点记录,如果不区分叶子节点和非叶子节点,通通把节点代表的页面放到申请的区中,那么扫描效果就大打折扣,而段可以让叶子节点的数据页尽可能连续和差距不那么大。所以InnoDB对B+树的叶子节点和非叶子节点进行区别对待,叶子节点和非叶子节点各有自己独有的区。而存放叶子节点的区的集合就算是一个段,存放非叶子节点的区的集合也算是一个段。即一个索引会生成两个段:一个叶子节点段和一个非叶子节点段。
原因二:
以完整的区为单位分配给某个段时,对于数据量较小的表来说太浪费存储空间。因为当段以区为单位申请存储空间时,由于一个区默认占用1MB存储空间以及一个聚簇索引会生成两个段,所以默认情况下只存放了几条记录的小表也需要2MB的存储空间,这就有点浪费了。
原因三:
出现上述问题的根源是:区中的所有页面都是为了存储同一个段的数据而存在,即使区的页面用不完也不能作他用。于是InnoDB便有了碎片区。在一个碎片区中,并非所有页都是为了存储同一个段的数据而存在,碎片区的页可以用于不同的目的的。比如有些页属于段A、有些页属于段B、有些页甚至不属于任何段。碎片区直属于表空间,不属于任何段。
原因四:
为某个段分配存储空间的策略:刚开始向表中插入数据时,段是从某个碎片区中以单个页面为单位来分配存储空间的。当某个段已经占用了32个碎片区页面后,就会以完整的区为单位来分配存储空间,原先占用的碎片区页面并不会被复制到新申请的完整的区中。所以说,段是一些零散的页面以及一些完整的区的集合。
(3)区Extend
一.区是什么
区由连续页组成的空间,一个区的大小是1M,一个区有64个连续的页。为了保证区中页的连续性,扩展时InnoDB一次从磁盘申请4~5个区。无论是系统表空间还是独立表空间,都可看成是由若干个连续的区组成。当一个段使用了32个碎片页后才是以区来分配,每256个区被分成一组。
二.为什么引入区
原因一:
向表中插入一条记录,本质上就是向该表的聚簇索引以及所有二级索引代表的B+树的节点中插入数据。而B+树每一层中的页都会形成一个双向链表,如果以页为单位来分配存储空间,那么双向链表中相邻的两个页之间的物理位置可能离得非常远。
原因二:
使用B+树来减少记录的扫描行数的过程是:通过一些搜索条件,到B+树的叶子节点中定位到第一条符合该条件的记录,然后沿着由记录组成的单向链表以及由数据页组成的双向链表,一直向后进行扫描。全表扫描就是定位到第一个叶子节点的第一条记录。
原因三:
如果双向链表中相邻的两个页的物理位置不连续,对于传统的机械硬盘来说,需要重新定位磁头位置,也就是会产生随机IO,影响性能。所以应尽量让页面链表中相邻的页的物理位置也相邻,以便扫描叶子节点的大量记录时可以使用顺序IO。
原因四:
为了尽量消除随机IO才引入了区的概念,一个区就是物理位置上连续的64个页,区中页面的页号都是连续的。当表中的数据量很大时,为某个索引分配空间时就不再按页为单位来分配了,而是按照区为单位进行分配。甚至当表中的数据非常非常多的时候,可以一次性分配多个连续的区,以消除更多的随机IO,但会造成一点空间的浪费。
(4)页Page
一.页是什么
区是由连续的页(Page)组成的空间,一个页的存储大小为16K,页用于存储多个Row行记录。
二.页的类型
页有很多种类型,如数据页、Undo页、系统页、事务数据页、大的BLOB对象页。
(5)行Row
InnoDB的数据是按行的方式进行存放的,每个页存放的行记录最多允许存放16K / 2 - 200行的记录,即每个页最多存放7992行记录。每行记录根据不同的行格式、不同的数据类型,会有不同的存储方式。
行包含的内容:记录的字段值、事务ID、回滚指针、字段指针等信息。
2.Page结构
(1)页结构各部分说明
(2)页结构整体划分
Page是InnoDB存储的最基本构件,也是InnoDB磁盘管理的最小单位,与数据库相关的所有内容都存储在这种Page结构里。
Page分为几种类型,常见的页类型有:数据页(B+Tree Node)、Undo页(Undo Log Page)、系统页(System Page)、事务数据页(Transaction System Page)等。
(1)页结构各部分说明
一.File Header字段用于记录Page的头信息
其中比较重要的是FIL_PAGE_PREV和FIL_PAGE_NEXT字段。通过这两个字段,就可以找到该页的上一页和下一页。实际上所有页通过两个字段可以形成一条双向链表。
二.Page Header字段用于记录Page的状态信息
三.Infimum和Supremum是最小和最大行记录
Infimum(下确界)记录比该页中任何主键值都要小的值,Supremum(上确界)记录比该页中任何主键值都要大的值,这两个伪记录构成了页中记录的边界。
四.User Records存放的是实际的数据行记录
五.Free Space中存放的是空闲空间
被删除的行记录会成为空闲空间。
六.Page Directory记录与二叉查找相关的信息
七.File Trailer存储检测数据完整性的数据
(2)页结构整体划分
页结构整体上可以分为三大部分,分别为:通用部分(文件头、文件尾)、数据记录部分、页目录部分。
一.通用部分(File Header&File Trailer)
通用部分主要指文件头和文件尾,将页的内容进行封装。通过文件头和文件尾校验的CheckSum方式可以确保页的传输是完整的,这时候就可以确认是否发生页断裂也就是页是否写失效了。
其中比较重要的是在文件头中的FIL_PAGE_PREV和FIL_PAGE_NEXT字段,通过这两个字段,可以找到该页的上一页和下一页,因此所有页可以形成一条双向链表。
二.数据记录部分(User Records&Free Space)
由于页的主要作用是存储记录,所以"最小和最大记录"和"用户记录"部分占了页结构的主要空间。另外空闲空间是个灵活的部分,当有新的记录插入时,会从空闲空间中进行分配用于存储新记录。
三.页目录部分(Page Directory)
数据页中的行记录会按照主键值由小到大顺序串联成一个单链表,单链表的链表头为最小记录,链表尾为最大记录。数据页目录中会顺序存储每一条行记录的地址,通过对数据页目录使用二分法,就能快速定位到查找的行记录。
3.行记录格式
(1)行格式分类
(2)COMPACT行记录格式
(3)Compact中的行溢出机制
(4)其他行格式记录
(1)行格式分类
表的行格式决定了它的行是如何物理存储的,这反过来又会影响查询和DML操作的性能。
如果在单个Page页中容纳更多行,那么查询和索引查找就能更快地工作,并且缓冲池中所需的内存会更少,写入更新时所需的IO也会更少。
InnoDB存储引擎支持四种行格式:Redundant、Compact、Dynamic和Compressed。
下面查询MySQL使用的行格式,MySQL5.7后默认是dynamic。
mysql> show variables like 'innodb_default_row_format';
+---------------------------+---------+
| Variable_name | Value |
+---------------------------+---------+
| innodb_default_row_format | dynamic |
+---------------------------+---------+
下面是指定行格式的语法:
CREATE TABLE <table_name(column_name)> ROW_FORMAT=行格式名称
ALTER TABLE <table_name> ROW_FORMAT=行格式名称
(2)COMPACT行记录格式
Compact设计目标是高效地存储数据,一个页中存放的行数据越多,其性能就越高。Compact行记录由两部分组成:记录的额外信息和记录的真实数据。
一.记录额外信息部分
服务器为了描述一条记录而添加了一些额外信息(元数据信息),这些额外信息分为3类,分别是:变长字段长度列表、NULL值列表和记录头信息。
第一类:变长字段长度列表
MySQL支持一些变长的数据类型,比如VARCHAR(M)、VARBINARY(M)、各种TEXT类型,各种BLOB类型。这些变长的数据类型占用的存储空间分两部分:真正的数据内容和占用的字节数。
变长字段的长度是不固定的,所以在存储数据时要把这些数据占用的字节数也存起来。读取数据时才能根据这个长度列表去读取对应长度的数据。
在Compact行格式中:会把所有变长类型的列的长度都存放在记录的开头部位形成一个列表,按照列的顺序逆序存放,这个列表就是变长字段长度列表。
第二类:NULL值列表
表中的某些列可能会存储NULL值,如果把这些NULL值都放到记录的真实数据中会比较浪费空间,所以Compact行格式把这些值为NULL的列存储到NULL值列表中。如果表中所有列都不允许为 NULL,就不存在NULL值列表。
第三类:记录头信息
记录头信息是由固定的5个字节组成,5个字节也就是40个二进制位,不同的位代表不同的意思。
delete_mask:这个属性标记着当前记录是否被删除,占用1个二进制位。值为0时代表记录并没有被删除,值为1时代表记录被删除掉。
min_rec_mask:标记该记录是否是B+树的每层非叶子节点中的最小记录。
n_owned:代表每个分组里,所拥有的记录的数量,一般是分组里主键最大值才有的。
heap_no:在数据页的User Records中插入的记录是一条条紧凑排列的,这种紧凑排列的结构又被称为堆。为了便于管理这个堆,把记录在堆中的相对位置给定一个编号heap_no,所以heap_no这个属性表示当前记录在本页中的位置。
record_type:这个属性表示当前记录的类型,一共有4种类型的记录。0表示普通用户记录、1表示B+树非叶节点记录、2表示最小记录、3表示最大记录。
next_record :表示从当前记录的真实数据到下一条记录的真实数据的地址偏移量,可以理解为指向下一条记录地址的指针。值为正数说明下一条记录在当前记录的后面,值为负数说明下一条记录在当前记录的前面。
二.记录真实数据部分
除了记录真实数据以外,MySQL还会为每条行记录添加一些列。这些列被称为隐藏列,具体的列如下:
列说明如下:
生成隐藏主键列的步骤:
步骤一:
服务器会在内存中维护一个全局变量,每当向某个包含隐藏的row_id列的表中插入一条记录时,就会把该变量的值当作新记录的row_id列的值,并且把该变量自增1。
步骤二:
每当这个变量的值为256的倍数时,就会将该变量的值,刷新到系统表空间的页号为7的页面中一个Max Row ID的属性中。
步骤三:
系统启动时会将页中的Max Row ID属性加载到内存中,并将该值加上256后赋值给全局变量,因为在上次关机时该全局变量的值可能大于页中Max Row ID属性值。
(3)Compact中的行溢出机制
一.什么是行溢出
MySQL中是以页为基本单位进行磁盘与内存之间的数据交互的。一个页的大小是16K = 16384字节。一个varchar(m)类型列最多可以存储65532个字节,一些大的数据类型如TEXT可以存储更多。如果一个表存在这样的大字段,那么一个页就无法存储一条完整的记录。这时就会发生行溢出,多出的数据就会存储在另外的溢出页中。
总结:如果某些字段信息过长,无法存储在B树节点中。这时候会被单独分配空间,此时被称为溢出页,该字段被称为页外列。
二.Compact中的行溢出机制
InnoDB规定一页至少存储两条记录(B+树特点),如果页中只能存放下一条记录,InnoDB会自动将行数据存放到溢出页中。当发生行溢出时,数据页只保存前768字节的前缀数据,接着是20个字节的偏移量,指向行溢出页。
(4)其他行格式记录
一.DYNAMIC和COMPRESSED行记录格式
DYNAMIC和COMPRESSED新格式引入的功能有:数据压缩、增强型长列数据的页外存储和大索引前缀。
Compressed和Dynamic行记录格式与Compact行记录格式是类似的。区别是在处理行溢出时:数据页不会存储真实数据的前768字节(完全溢出),而只存储20个字节的指针来指向溢出页。
Compressed与Dynamic相比:Compressed存储的行数据会以zlib的算法进行压缩以节省空间,因此对于BLOB、TEXT、VARCHAR这类大长度类型的数据能有效存储。MySQL5.7默认的行记录格式是Dynamic。
二.Redundant
Redundant是MySQL 5.0版本前InnoDB的行记录存储方式。Redundant行记录的格式是:首部是一个字段长度偏移列表,同样是按照列的顺序逆序放置的,该条记录中所有列的长度信息都按照逆序存储到字段长度偏移列表,这些列当然包括隐藏列、NULL值列等。