MySQL中的数据结构

369 阅读10分钟

资料来源:

内容基本与Jeremy Cole的博客内容相同,用于个人整理理解,建议直接阅读以下资料进行学习。

索引组织表

在InnoDB上,表都是根据主键顺序组织存放的,被称为索引组织表

数据即索引,索引即数据

主键设置:

  • 显式定义
  • 是否存在非空的唯一索引
  • 自动创建一个6字节大小的指针

优先级由上至下

逻辑储存结构

image-20210410001025330

表空间

InnoDB中逻辑储存结构的最高层

img

表空间是一个抽象的概念,对于系统表空间来说,对应着操作系统层次文件系统中一个或多个实际文件;对于每个独立表空间来说,对应着文件系统中一个名为表名.ibd的实际文件。

Ibd 文件实际上是一个功能齐全的空间,可以包含多个表,但在 MySQL 的实现中,它们只包含一个表。

在逻辑上,所有数据都被存放在表空间

表空间被划分为许多连续的区,每个区默认由64个页组成,每256个区划分为一组,每个组的最开始的几个页面类型是固定的就好了。

  • 系统表空间(system tablespace)。文件以ibdata1、ibdata2等命名,包括元数据数据字典(表、列、索引等)、double write buffer、插入缓冲索引页(change buffer)、系统事务信息(sys_trx)、默认包含undo回滚段(rollback segment)。
  • 用户表空间。innodb_file_per_table=true时,一个表对应一个独立的文件,文件以db_name/table_name.ibd命名。行存储在这类文件。另外还有5.7之后引入General Tablespace,可以将多个表放到同一个文件里面。
  • redo log。文件以ib_logfile0、ib_logfile1命名,滚动写入。主要满足ACID特性中的Durablity特性,保证数据的可靠性,同时把随机写变为内存写加文件顺序写,提高了MySQL的写吞吐。
  • 另外还可能存在临时表空间文件、undo独立表空间等。

表空间格式

表空间(tablespace)有一个32位的spaceid,用户表空间物理上是由page连续构成的,每个page的序号是一个32位的uint,page 0位于文件物理偏移量0处,page 1位于16384偏移量处。由此推出InnoDB单表最大2^32 * 16k = 64T。

表的所有行数据都存在页类型为INDEX的索引页(page)上,为了高效管理表空间,InnoDB以extent为单位申请page使用。

又以256个extent为一段,每段还需要很多其他的辅助页,例如文件管理页FSP_HDR/XDES、插入缓冲IBUF_BITMAP页、INODE页等,用于记录来跟踪所有的页面、区段和表空间本身。

image.png

  • fsp_hdr:空间中的第一页(0页)总是 fsp_hdr 页。Fsp_hdr 页面包含一个 FSP 标头结构,用于跟踪空间大小以及空闲、片段和完整范围的列表等内容。一个 fsp_hdr 页面内部只有足够的空间存储256个区段(或16,384页,256 MiB)的信息。
  • XDES:XDES 和 fsp_hdr 页面的结构是相同的,只是在 XDES 页面中,FSP 头部结构是归零的。
  • ibufbitmap:
  • INODE:用于存储与文件段相关的列表

后续在page的章节中详细说明

系统表空间

用户表空间

image.png

空闲空间管理

数据段、索引段、回滚段

索引段即B+树的的叶子节点

对B+树叶子节点中的记录进行顺序扫描,而如果不区分叶子节点和非叶子节点,统统把节点代表的页面放到申请到的区中的话,进行范围扫描的效果就大打折扣了。

为了将叶子节点数据放在连续的物理结构中,对叶子节点有自己独有的区,非叶子节点也有自己独有的区。存放叶子节点的区的集合就算是一个段(segment),存放非叶子节点的区的集合也算是一个段。也就是说一个索引会生成2个段,一个叶子节点段,一个非叶子节点段

更高效的页管理:区和组

由连续页组成 任何情况下都为1MB

为保证区中页的连续性,InnoDB储存引擎一次从磁盘申请4~5个区。

一般页的大小为16KB,一个区中由64个连续的页

区的分类:

  • 空闲的区:现在还没有用到这个区中的任何页面。
  • 有剩余空间的碎片区:表示碎片区中还有可用的页面。
  • 没有剩余空间的碎片区:表示碎片区中的所有页面都被使用,没有空闲页面。
  • 附属于某个段的区。每一个索引都可以分为叶子节点段和非叶子节点段,除此之外InnoDB还会另外定义一些特殊作用的段,在这些段中的数据量很大时将使用区来作为基本的分配单位。

256个区被划分为一个组

是InnoDB管理磁盘的最小单位,也是与内存交互的基本单位

默认16KB,可以通过innodb_page_size设置为4KB、8KB

  • 数据页(B-tree Node)
  • undo页
  • 系统也
  • 事务数据页
  • 插入缓冲位图页
  • 插入缓冲空闲列表页
  • 未压缩的二进制大对象页
  • 压缩的二进制大对象页

文件管理页

文件管理页的页类型是FSP_HDR和XDES(extent descriptor),用于分配、管理extent和page。 img

默认一个extent(1MB大小)管理64个物理连续的page(16k),extent是InnoDB高效分配扩容page的机制。如果page更小(例如8k,4k),则仍然要保证extent最小1M,page数就会相应变多;如果page变大(例如32k),则仍然是64个page。

FSP_HDR/XDES页在表空间中的位置,和内部结构如下。

FSP_HDR页都是page 0,XDES页一般出现在page 16384, 32768等固定的位置。一个FSP_HDR或者XDES页大小同样是16K,容量限制所能管理的extent必定是有限的,一般情况下,每个extent都有一个占40字节的XDES entry描述维护,因此1个FSP_HDR页最多管理256个extent(也就是256M,16384个page)。那么随着表空间文件越来越大,就需要更多的XDES页。

XDES entry存储所管理的extent状态:

  • FREE(空)
  • FREE_FRAG(至少一个被占用)
  • FULL_FRAG(满)
  • 归某个segment管理的信息

即上述extent的四种状态

XDES entry还存储了每个extent内部page是否free(有空间)信息(用bitmap表示)。XDES entry组成了一个双向链表,同一种extent状态的收尾连在一起,便于管理。

FSP_HDR和XDES的唯一区别,FSP Header只有在page 0 FSP_HDR中有值。

而FSP Header里面最重要的信息就是四个链表头尾数据(FLST_BASE_NODE结构,FLST意思是first and last),FLST_BASE_NODE如下。

  • 当一个Extent中所有page都未被使用时,挂在FSP_FREE list base node上,可以用于随后的分配;
  • 有一部分page被写入的extent,挂在FREE_FRAG list base node上;
  • 全满的extent,挂在FULL_FRAG list base node上;
  • 归属于某个segment时候挂在FSEG list base node上。

当InnoDB写入数据的时候,会从这些链表上分配或者回收extent和page,这些extent也都是在这几个链表上移动的。

INODE页

一般而言,INODE一定会出现在文件的page 2上,如果管理的索引过多,才会分配更多的INODE页。

image.png

segment是表空间管理的逻辑单位。INODE页就是用于管理segment的,每个Inode entry负责一个segment。

一个segment由32个碎片页(fragment array),FSEG_FREE、FSEG_NOT_FULL、FSEG_FULL组成,这些信息记录在Inode entry里,可以简单理解为Inode就是segment元信息的载体。

FREE、NOT_FULL、FULL三个FLST_BASE_NODE对象和FSP_HDR/XDES页里面的FSP_FREE、FREE_FRAG、FULL_FRAG、FSEG概念类似。这些链表被InnoDB使用,用于高效的管理页分配和回收。

INDEX数据索引页

每个索引页面的总体结构如下:

  • File Header 页的通用信息
  • Page Header 数据页专有信息
  • infimun 、Supermun Records 最大记录和最小记录
  • User Records 用户记录
  • Free Space 空闲空间
  • Page Directory 页目录
  • File Trailer 文件尾部 校验是否完整

业内数据的组织方式

image_1d6g64af2sgj1816ktl1q22dehp.png-189.1kB

小结

image.png

行记录格式

image.png

Compact

image-20210410002023388

由可选的两个标识+record header+body组成,具体如下。

除了记录实际数据外,还有描述这行信息的额外信息

  • 变长字段
  • NULL值列表
  • 记录头信息

变长字段长度列表

Variable field lengths: 可选标识,变长字段长度,如果没有变长字段,就不存在。每个变长字段都用1-2个字节表示长度,根据列定义顺序逆序存放,如果小于等于127,则1个字节;如果小于等于127,则1个字节;大于127,低字节下一位的表示是否有overflow page存储,剩余6位和高字节的8位,按照大尾端encoding组成变长长度。

null值标志位

记录行内数据 为null值的位置,真实数据中不会保存null值

先统计允许储存null的列

Nullable field bitmap:可选标识,表明哪些列是NULL,如果没有nullable字段,就不存在。一个字节能表示8个nullable字段,超过8个字段就扩充到低字节。如下图所示,18个字段,9个可为空,如果其中某3个实际为空,则两个字节存储如图。

记录头信息

image-20210411150031143

record header: 固定5个字节长度。

Info Flags:1个字节。低4位表示是否min_rec或者deleted。高4位表示num of records owned,与上面提到的page directory呼应,如果被page directory slot指向,则有值。

2个大尾端字节:低三位表示类型, 包括普通记录REC_STATUS_ORDINARY=0,非叶子节点记录REC_STATUS_NODE_PTR=1,起始虚拟记录REC_STATUS_INFIMUM=2,终点虚拟记录REC_STATUS_SUPREMUM=3。高5位表示heap no,即顺序位置。

2字节next record offset: 直接定位到下一个record的数据部分,也就是主键偏移量,而不是record header。

可以看出如果表结构没有变长字段,没有nullable字段,则不会存在冗余信息。5个字节长度的record header是必须有的,上面提到的infimum和supremum也是一种特殊的row,只不多对用户不可见。

隐藏列

image-20210411152744619

t_id、roll_pointer这两个值用来支持MVCC机制,事务ID是实现事务隔离级别的基础,而通过回滚指针指向undo log,可实现非锁定一致性读。

数据列

image-20210411153007777

索引

序列化后存储于此,例如int类型索引主键就占用4个字节。

对于聚簇索引的叶子节点,存储行。

对于二级索引的叶子节点,存储行的主键值。

对于聚簇索引和二级索引的非叶子节点,存储child page最小的key。

上面提到的infimum和supremum中就只存字符串在行数据里。

非主键列的数据

对于聚簇索引的叶子节点,是按照表结构定义排列的columns,每种column类型都有自己的encoding方法。

对于二级索引的叶子节点,是行的主键值。

对于聚簇索引和二级索引的非叶子节点,是child page number。