0%

『译』InnoDB中的数据组织

前言

本文翻译自《Data Organization in InnoDB》,原文属于InnoDB官方blog,后来Oracle修改了官方blog的地址,没有找到原文链接,如果哪位找到原文链接,烦请告知。另本人水平有限,如发现翻译错误或有问题的地方,也欢迎指出。

简介

InnoDB会创建很多种文件,这里我们来看一下类似表空间、页、段、扇区这类的逻辑数据组织。我们将会详细介绍这几种数据组织类型,并讨论它们之间的关系。最后,我们会看一下InnoDB存储引擎内部的数据布局的高级视图。

文件

MySQL将所有数据都保存在自己的data目录中,data目录可以通过命令参数–data-dir来设置,也可以在配置文件中指定。详细请参考MySQL的命令选项介绍。

默认情况下,InnoDB在初始化的时候,会在data目录下创建三个比较重要的文件:ibdata1、ib_logfile0、ib_logfile1。其中,ibdata1文件用来保存系统数据和用户数据,ib_logfile0和ib_logfile1是redolog文件。这三个文件的存放位置以及各自的文件大小都是可配置的。可以参考InnoDB配置。

ibdata1文件属于表空间id为0的系统表空间,系统表空间可以拥有一个或多个数据文件。在MySQL 5.6版本,只有系统表空间可以拥有多个数据文件,其他所有表空间都只能有一个数据文件,而且,也只有系统表空间可以拥有多个表,其他表空间都只能有一个表。 数据文件ibdata和redolog文件ib_logfile在内存中使用C语言结构体file_node_t来表示。

表空间

默认情况下,InnoDB只包含一个ID为0的系统表空间,可以使用使用innodb_file_per_table来创建更多的系统表空间。在MySQL 5.6中,这个参数默认为ON,也就是说,每个表在自己的表空间中拥有独立的数据文件。

在InnoDB源文件storage/innobase/fil/fil0fil.cc的注释中,解释了表空间和数据文件之间的关系:

一个表空间包含了一系列的文件。由于最后的未完成的block未被使用,因此,这些文件的大小不一定会被block大小整除。当一个新文件被追加到表空间时,文件大小的最大值就已经被指定了。为了避免表空间大小不够时动态的扩展文件大小,在文件创建的时候,就把文件大小预扩展到其最大值。

上面最后一句话,避免动态扩展而预分配只应用与redolog文件,对于数据文件来说,还是动态扩展的。当然,跟前面说的一样,只有系统表空间可以包含多个数据文件。

这里需要明确一点:即使系统表空间可以包含多个数据文件,这些文件还是被当成一个串联起来的大文件使用。因此,这些文件的顺序就显得比较重要了。

页(page)

一个数据文件在逻辑上被分成多个大小一致的page,第一个数据文件的第一个page的ID标记为0,下一个page ID为1,以此类推。在一个表空间中,一个page ID(page_no)唯一标识一个page。同样,一个表空间ID(space_id)唯一标识一个表空间。因此,在InnoDB中,使用二元组(space_id, page_no)来唯一标识一个page,使用三元组(space_id, page_no, page_offset)来访问任意位置,其中page_offset是page内的字节偏移。

一个表空间中不同数据文件的各个page有什么关系呢?在源码中的另一段注释是这样描述的:

使用一个32位无符号整数来确定一个表空间中一个block的位置,由于表空间中的所有数据文件是被串联起来的,因此,在这个串联的大文件中,地址为n的block就是文件中的第n个block(第一个block地址为0,文件末尾未完成的block碎片不会被计数)。可以在这个链的尾部追加新文件来扩展表空间。

上面这段话说明,在一个表空间中,并不是所有数据文件的第一个page_no都是0,只有第一个数据文件的第一个page的page_no才为0。上面也提到,page_no是一个32位的无符号整数,所以page_no在磁盘上被存储为4个字节。

每一个page都有一个头部结构page_header_t。详细请参考另一篇blog。

扇区(extents)

一个扇区是1MB连续的page,每个扇区的大小定义为:

1
#define FSP_EXTENT_SIZE (1048576U/UNIV_PAGE_SIZE)

其中,UNIV_PAGE_SIZE是一个编译时常量,从5.6开始,是一个全局变量。一个扇区page的个数,取决于使用的page大小。如果page大小是16KB(默认),那么一个扇区就包含64个page。

page的类型

一个page有多种用途,page的类型标识着page的使用目的。page类型存储在每个page的头部中,page类型定义在源文件:storeage/innobase/include/fil0fil.h. 下面的表格,提供了page类型的简单描述。

page类型 描述
FIL_PAGE_INDEX 该类型page是一个B-tree节点
FIL_PAGE_UNDO_LOG 该类型page存储undo log
FIL_PAGE_INODE 该类型page包含一组fseg_inode_t对象
FIL_PAGE_IBUF_FREE_LIST 该类型page在插入buffer和修改buffer的空闲列表中
FIL_PAGE_TYPE_ALLOCATED 该类型page属于新分配的page
FIL_PAGE_IBUF_BITMAP 该类型page保存插入buffer和修改buffer的bitmap信息
FIL_PAGE_TYPE_SYS 系统page
FIL_PAGE_TYPE_TRX_SYS 事务系统数据
FIL_PAGE_TYPE_FSP_HEADER 文件空间头部
FIL_PAGE_TYPE_XDES 扇区描述符page
FIL_PAGE_TYPE_BLOB 未压缩的blob page
FIL_PAGE_TYPE_ZBLOB 第一个压缩的blob page
FIL_PAGE_TYPE_ZBLOB2 子序列压缩的blob page

每一种类型的page都有不同的用途。详细探讨每种page的用途已经超出了本文的范围。到这里为止,已经可以充分的看到,所有的page都会有一个page头部,在page头部存储着头部类型,page的类型决定的page内部数据的布局和内容格式。

表空间头部

每一个表空间,都有一个fsp_header_t类型的头部信息,这个数据结构存储在表空间的第一个page中。主要包括以下内容:

  1. 表空间ID(space_id)
  2. 表空间中page的个数
  3. 空闲extents链表
  4. 不属于任何段的完整extents链表
  5. 不属于任何段的部分完整或者部分空闲的extents链表
  6. 包含段头部的page列表,预留了所有段的inode slots
  7. 包含段头部的page列表,预留了非所有段的inode slots

fsp_header_t结构体

通过表空间头部,可以直接访问表空间中可使用段的链表。表空间头部占用的字节数在宏FSP_HEADER_SIZE定义,大小等于16 × 7 = 112字节。

表空间预留page

前面已经提到,Innodb存在一个ID为0的系统表空间。这是一个特殊的表空间,在MySQL运行期间会一直保持打开状态。系统表空间的前面一些page会预留给系统内部使用。这些信息可以在头文件storage/innobase/include/fsp0types.h。下面列出一些简单的描述。

page number page name 描述
0 FSP_XDES_OFFSET extent表述符page
1 FSP_IBUF_BITMAP_OFFSET 插入buffer的bitmap page
2 FSP_FIRST_INODE_PAGE_NUM 第一个inode节点的page 号
3 FSP_IBUF_HEADER_PAGE_NO 系统表空间中插入buffer的头部page
4 FSP_IBUF_TREE_ROOT_PAGE_NO 系统表空间中插入buffer的root page
5 FSP_TRX_SYS_PAGE_NO 系统表空间中事务系统的头部
6 FSP_FIRST_RSEG_PAGE_NO 系统表空间中第一个回滚段的page
7 FSP_DICT_HDR_PAGE_NO 系统表空间中数据字段的头部page

当配置项innodb_file_per_table开启的时候,每一个表会有一个独立的系统表空间对应一个数据文件。函数dict_build_table_def_step()—->dict_build_tablespace_for_table()中相关注释如下:

1
2
3
4
5
6
7
/* We create a new single-table tablespace for the table.
We initially let it be 4 pages:
- page 0 is the fsp header and an extent descriptor page,
- page 1 is an ibuf bitmap page,
- page 2 is the first inode page,
- page 3 will contain the root of the clustered index of the table we create here.
*/

文件段

一个表空间包含很多文件段,文件段是一个逻辑上的概念。每个段都有一个指向该文件段inode节点(fseg_inode_t)的段头部(fseg_header_t)。文件段头部包括以下信息:

  1. inode属于哪个表空间
  2. inode的page_no
  3. inode偏移的字节数
  4. 文件段头部的长度(以字节为单位)

fseg_inode_t对象包括以下信息:

  1. inode所属的段id
  2. 完整的extents列表
  3. 这个段的空闲extents列表
  4. 部分满/空闲的extents列表
  5. 属于该段的独立page数组,数组的大小是一个extent的一半。

当一个段增长的时候,它会从所属的表空间中获取空闲的extent或page。

当一个表被创建的时候,innodb内部会创建一个BTree结构的聚簇索引。它包括连个文件段,一个存储非叶子page,另一个存储叶子page。

给定一个表,聚簇索引的Btree的root page会从数据字典中获取。因此,在innodb中,每个表存在于一个表空间中,它包括一个BTree(聚簇索引),这个BTRee有两个文件段,每个文件段可以包含很多extents,每个extent包括1MB的连续page。

小结

本文详细讨论了InnoDB内部的数据组织。首先介绍了InnoDB创建的文件类型,接着介绍了各种逻辑概念,包括表空间、page、page类型、extents、段和表,也介绍了这些逻辑概念之间的相互关系。

如果对您有帮助