InnoDB 表空间 (table space) 用来组织存储保存的数据,本文中对表空间管理进行分析。
简介
InnoDB 实现的表空间是在文件系统之上又构建的一层逻辑存储的空间管理,每个表空间在逻辑上划分为了如下的几层结构,依次包括 table space
、segment inode
、extent
、page
、record
。
如图所示,InnoDB 最小读写单位是 Page;为了高效管理,又将连续的 64 个页称为 extent
,而且在一个表空间中,通常是以 extent
为单位进行资源分配。
除此之外,还在 extents 上层添加了一个 segment inode
管理,最多可以管理 256
个 extents
。如果 page 采用默认的 16K
,那么对应的 extent
大小为 64*16K=1M
,而 segment
对应 256M
。
参数设置
首先确认几个与表空间管理相关的参数,也就是打开独立表空间,且采用默认的页大小为 16K 。
mysql> SHOW VARIABLES LIKE 'innodb_file_per_table';
+-----------------------+-------+
| Variable_name | Value |
+-----------------------+-------+
| innodb_file_per_table | ON |
+-----------------------+-------+
1 row in set (0.01 sec)
mysql> SHOW VARIABLES LIKE 'innodb_page_size';
+------------------+-------+
| Variable_name | Value |
+------------------+-------+
| innodb_page_size | 16384 |
+------------------+-------+
1 row in set (0.00 sec)
工具
在查看表空间时可以使用几个常用的工具,可以参考 innodb_page_info 。
----- 查看每个页的详细信息
$ innodb_page_info -v dept_emp.ibd
文件格式详解
InnoDB 的存储文件格式如上图所示,我们直接从大到小介绍其结构,依次从 space file -> segment -> extent -> page -> row
逐一介绍。
Space File 格式
Space File 主要包括了两类:系统空间 (system space) 和 表空间 (Per-table space files)。一个表空间文件,实际就是一系列 Pages 的组合 (最大 232),为了方便管理,你可以理解为做了三级的划分,分别是 segment
、extent
、page
。
其基本结构如下所示:
0 KiB +---------------------------------------------------+
| FSP_HDR: Filespace Header / Extent Descriptor |
16 KiB +---------------------------------------------------+
| IBUF_BITMAP: Insert Buffer Bookeeping |
32 KiB +---------------------------------------------------+
| INODE: Index Node Information |
48 KiB +---------------------------------------------------+
| | |
| ~ More pages ... ~
v | |
256 MiB +---------------------------------------------------+
| | XDES: Extent descriptor for next 16,384 pages |
| +---------------------------------------------------+
| | IBUF_BITMAP: IBUF Bitmap for next 16,384 pages |
| +---------------------------------------------------+
| | |
| ~ More pages ... ~
v | |
512 MiB +---------------------------------------------------+
| | XDES: Extent descriptor for next 16,384 pages |
| +---------------------------------------------------+
| | IBUF_BITMAP: IBUF Bitmap for next 16,384 pages |
| +---------------------------------------------------+
| | |
| ~ More pages ... ~
| | |
v +---------------------------------------------------+
对于 Page0
肯定是 File SPace HeaDeR
,其中维护了 extent
相关的信息,例如那些是空闲的、满的、有碎片的 (fragmented)。在 FSP_HDR
页中,只能维护 256
个 extent
相关的信息 (16,384 pages==256MiB),这也就导致每隔 256M 都需要有一个 XDES 来管理接下来的 extent 元信息。
系统空间
在系统空间中的固定位置保存了与 InnoDB 相关的关键信息,其中的 FSP_HDR
、IBUF_BITMAP
、INODE
三个页都是相同的,只是之后的页会有区别。
0 KiB +---------------------------------------------------+
| FSP_HDR: Filespace Header / Extent Descriptor |
16 KiB +---------------------------------------------------+
| IBUF_BITMAP: Insert Buffer Bookeeping |
32 KiB +---------------------------------------------------+
| INODE: Index Node Information |
48 KiB +---------------------------------------------------+
| SYS: Insert Buffer Header |
64 KiB +---------------------------------------------------+
| | INDEX: Insert Buffer Header |
| +---------------------------------------------------+
| | TRX_SYS: Transaction System Header |
| +---------------------------------------------------+
| | SYS: First Rollback Segment |
| +---------------------------------------------------+
| | SYS: Data Dictionary Header |
| +---------------------------------------------------+
| | |
| ~ More pages ... ~
v | |
Page 64 +---------------------------------------------------+
| Double Write Buffer Block 1 (64 pages) |
Page 128 +---------------------------------------------------+
| Double Write Buffer Block 2 (64 pages) |
Page 192 +---------------------------------------------------+
| | |
| ~ More pages ... ~
| | |
v +---------------------------------------------------+
表空间 Per-table Space Files
这需要打开 innodb_file_per_table
变量之后才会生效。
0 KiB +---------------------------------------------------+
| FSP_HDR: Filespace Header / Extent Descriptor |
16 KiB +---------------------------------------------------+
| IBUF_BITMAP: Insert Buffer Bookeeping |
32 KiB +---------------------------------------------------+
| INODE: Index Node Information |
48 KiB +---------------------------------------------------+
| INDEX: Root page of first index |
64 KiB +---------------------------------------------------+
| | INDEX: Root page of second index |
| +---------------------------------------------------+
| | INDEX: Node pages ... |
| +---------------------------------------------------+
| | INDEX: Leaf pages ... |
| +---------------------------------------------------+
| | ALLOCATED: Reserved bu unused pages ... |
| +---------------------------------------------------+
| | |
| ~ More pages ... ~
| | |
v +---------------------------------------------------+
Segment
在 segment
中保存了与 extent
相关的元数据,实际上是在空间文件的固定位置保存了 extent
的元数据信息,其格式为 FSP_HDR/XDES
,其结构也非常简单,在 Page Body 中保存了 256 个 extent descriptors 。
00000 +---------------------------------------------------+
| FIL header (38) |
00038 +---------------------------------------------------+
| FSP Header (zero-filled for XDES pages) (112) |
00150 +---------------------------------------------------+
| XDES Entry 0 (pages 0 ~ 63) (40) |
00190 +---------------------------------------------------+
| XDES Entry 1 (pages 64 ~ 127) (40) |
00230 +---------------------------------------------------+
| XDES Entry 2 (pages 128 ~ 191) (40) |
00270 +---------------------------------------------------+
| XDES Entry 3 (pages 192 ~ 255) (40) |
00310 +---------------------------------------------------+
| |
~ ... ... ~
| |
10310 +---------------------------------------------------+
| XDES Entry 254 (pages 16256 ~ 16319) (40) |
10350 +---------------------------------------------------+
| XDES Entry 255 (pages 16320 ~ 16383) (40) |
10390 +---------------------------------------------------+
| |
~ (Empty Space: 5,986 bytes) ~
| |
16376 +---------------------------------------------------+
| FIL Trailer (8) |
16384 +---------------------------------------------------+
该页的类型同样在 fil0fil.h
中定义。
#define FIL_PAGE_INODE 3 /*!< Index node */
该类型的页定义为 inode
,这与文件系统中的 inode
概念有点混淆,实际上与文件系统中的概念完全不同,在 InnoDB 中只是描述了与 extent 相关的信息。
Extents and extent descriptors
extent
是为了更加方便的管理 page
,也可以称为簇或者区,对于 16KiB
大小的 page
一个 extent
管理 64
个,其定义在 fsp0types.h
文件中定义,不同大小的页,对应的 extent
也有所不同。
/** File space extent size in pages
page size | file space extent size
----------+-----------------------
4 KiB | 256 pages = 1 MiB
8 KiB | 128 pages = 1 MiB
16 KiB | 64 pages = 1 MiB
32 KiB | 64 pages = 2 MiB
64 KiB | 64 pages = 4 MiB
*/
/** File space extent size (one megabyte if default two or four if not) in pages */
#define FSP_EXTENT_SIZE ((UNIV_PAGE_SIZE <= (16384) ? \
(1048576U / UNIV_PAGE_SIZE) : \
((UNIV_PAGE_SIZE <= (32768)) ? \
(2097152U / UNIV_PAGE_SIZE) : \
(4194304U / UNIV_PAGE_SIZE))))
extent 是通过一个叫簇描述符 (extent descriptor) 来表示的,对应了上述的 XDES Entry
,详细格式如下:
N +------------------------------------+
| File Segment ID (8) |
N+8 +------------------------------------+
| List node for XDES list (12) |
N+20 +------------------------------------+
| State (4) |
N+24 +------------------------------------+
| Page State Bitmap (16) |
| 2 bits per page, 1=free, 2=clean |
N+40 +------------------------------------+
详细的宏定义在 fsp0fsp.h
头文件中。
#define XDES_ID 0
#define XDES_FLST_NODE 8
#define XDES_STATE (FLST_NODE_SIZE + 8)
#define XDES_BITMAP (FLST_NODE_SIZE + 12)
#define XDES_SIZE (XDES_BITMAP + UT_BITS_IN_BYTES(FSP_EXTENT_SIZE * XDES_BITS_PER_PAGE))
每个 extent descriptor
会管理 64
个 pages
,最后的 16Bytes
用来标识这 64
个 page
的状态,如空闲、半空闲,主要目的是为了能够快速从这 64
个 page
中找到空闲的 page
。
Page
页是 InnoDB 中的最小的逻辑存储单位,很多基本操作都是以页为单位进行,在 include/univ.i
文件中通过宏指定,默认大小是 16K
。
#define UNIV_PAGE_SIZE ((ulint) srv_page_size)
而变量 srv_page_size
的大小,实际对应了 innodb_page_size
参数。与页相关的宏定义基本都在 fil0fil.h
头文件中,其中定义了多种页类型,其中比较常见的举例如下:
#define FIL_PAGE_INDEX 17855 // 数据索引页
#define FIL_PAGE_UNDO_LOG 2 // 事务回滚日志页
#define FIL_PAGE_INODE 3 // inode页,用来管理segment
如上所述,默认每个页的大小为 16K=16384Byte
,每个页通过一个 int32
类型的值标识其在表空间中页的偏移量,也就是说默认的表空间最大是 232 x 16 KiB = 64 TiB 。
页格式
所有 page 的格式都是相同的,由 page_header
、page_body
、page_trailer
三部分组成。
00000 +-------------------------------------------+
| FIL header (38) FIL_PAGE_DATA |
00038 +-------------------------------------------+
| Body, other headers and page data, |
| depending on page type. |
| |
| Total usable space: 16,338 bytes |
| |
16376 +-------------------------------------------+
| FIL Trailer (8) FIL_PAGE_DATA_END |
16384 +-------------------------------------------+
如上,包括了 page 的头信息,占 38 个字节,像页类型这样的元数据信息会保存在该字段中;page trailer 也就是页末尾的最后 8 个字节;剩余中间的部分作为 page body,也即页的实体信息,对这部分来说不同的页类型包含了不同的内容。
而对应页内各个 field 的大小,是在 fil0fil.h
头文件中通过宏进行定义。需要注意的是,很大一部分宏,实际定义的是在页内的偏移量,可以参考其注释。
#define FIL_PAGE_DATA 38 /*!< start of the data on the page */
#define FIL_PAGE_DATA_END 8 /*!< size of the page trailer */
页中其它的偏移量同样在 fil0fil.h
头文件中定义。
Header+Tailer
其中,Header 和 Tailer 中保存元信息的详细内容如下所示:
00000 +-------------------------------------------+
| Checksum (4) |
00004 +-------------------------------------------+
| Offset (Page Number) (4) |
00008 +-------------------------------------------+
| Previous Page Number (4) |
00012 +-------------------------------------------+
| Next Page Number (4) |
00016 +-------------------------------------------+
| LSN for last page modification (8) |
00024 +-------------------------------------------+
| Page Type (2) |
00026 +-------------------------------------------+
| Flush LSN OR version+checksum (8) |
00034 +-------------------------------------------+
| Space ID (4) |
00038 +-------------------------------------------+
| |
~ ... ... ~
| |
16376 +-------------------------------------------+
| Old-style Checksum (4) |
16380 +-------------------------------------------+
| Low 32 bit of LSN (4) |
16384 +-------------------------------------------+
在 Page Header 中,各个字段的占用空间以及大致的含义如下。
FIL_PAGE_SPACE_OR_CHKSUM # 4,对于大于4.0.14的版本,存储的为checksum,否则为0
FIL_PAGE_OFFSET # 4,页号page no,一般是在表空间的物理偏移量
FIL_PAGE_PREV # 4,前一页的page no,B+tree的叶子节点是通过链表串起来,很多类型的页不需要该值
FIL_PAGE_NEXT # 4,后一页的page no
FIL_PAGE_LSN # 8,更改记录时最大的redo log lsn,一般用在redo log恢复时使用
FIL_PAGE_TYPE # 2,page的类型
FIL_PAGE_FILE_FLUSH_LSN # 8,space文件最后被flush是的redo log lsn,只在第一页中设置
FIL_PAGE_SPACE_ID # 4,最后被归档的archive log file序号,同样只在space的第一个页中被设置
这里的页号 (page no) 实际上是相对于整个表空间的偏移量,表空间可以包含有多个文件,而这个页号是连续的。在各个页的头部中,还保存了前后页的 page no,从而形成了类似双像链表的结构。
不同类型的页对应 body 部分的数据结构是不同的,因此需要根据 header 中的页类型来解析该页的数据。另外,页号会在页初始化完成之后赋值,可以用来校验通过偏移量读取的页是否正确。
对于 tailer 来说,高 4 位是用来存储 FIL_PAGE_LSN 的部分信息,低 4 位用来表示 page 页中数据老的 checksum 信息。
checksum
在 5.6.3 中,引入了 innodb_checksum_algorithm 这一变量,用于设置 checksum 的计算方式,默认采用的是 INNODB 。其中 checksum 分别在 header 和 tailer 保存,在 tailer 中保存的是老的方式,后面将会被取消掉。
Index
在 InnoDB 中,所有的都是通过 Index 表示,包括主表和索引表,该类型的页同样在 fil0fil.h
中定义。
#define FIL_PAGE_INDEX 17855 /*!< B-tree node */
该页的结构如下。
00000 +-------------------------------+
| FIL header (38) |
00038 +-------------------------------+
| INDEX Header (36) |
00074 +-------------------------------+
| FSEG Header (20) |
00094 +-------------------------------+
| System Records (26) |
00120 +-------------------------------+
| |
~ User Records ~
| |
+-------------------------------+
Top | Free Space |
+-------------------------------+
| |
~ Page Directory ~
| |
16376 +-------------------------------+
| FIL Trailer (8) |
16384 +-------------------------------+
Index Header
38 +-------------------------------+
| Number of Directory Slots (2)
40 +-------------------------------+
| Heap Top Positon (2) |
42 +-------------------------------+
| Number of Heap Records / Format Flag (2)
44 +-------------------------------+
System Records
每个 INDEX 页都包含两个被称为 infimum
和 supremum
的系统记录,而且其偏移量是固定的,分别为 offset 99
以及 offset 112
。
094 +------------------------------------+
| Info Flags (4 bits) |
+------------------------------------+
| Number of Records Owned (4 bits) |
095 +------------------------------------+
| Order (13 bits) |
+------------------------------------+
| Record Type (3 bits) |
097 +------------------------------------+
| Next Record Offset (2) |
099 +------------------------------------+
| "infimum\0" (8) |
107 +------------------------------------+
| Info Flags (4 bits) |
+------------------------------------+
| Number of Records Owned (4 bits) |
108 +------------------------------------+
| Order (13 bits) |
+------------------------------------+
| Record Type (3 bits) |
110 +------------------------------------+
| Next Record Offset (2) |
112 +------------------------------------+
| "supremum\0" (8) |
120 +------------------------------------+
Record
记录头信息如下。
094 +------------------------------------+
| Info Flags (4 bits) |
+------------------------------------+
| Number of Records Owned (4 bits) |
095 +------------------------------------+
| Order (13 bits) |
+------------------------------------+
| Record Type (3 bits) |
097 +------------------------------------+
| Next Record Offset (2) |
099 +------------------------------------+
| "infimum\0" (8) |
107 +------------------------------------+
| Info Flags (4 bits) |
+------------------------------------+
| Number of Records Owned (4 bits) |
108 +------------------------------------+
| Order (13 bits) |
+------------------------------------+
| Record Type (3 bits) |
110 +------------------------------------+
| Next Record Offset (2) |
112 +------------------------------------+
| Next Record Offset (2) |
N +------------------------------------+
其中 index page body
由 5 部分组成:body header
、recorders
、free recorders
、free heap
和 page directory
,其中 body header
的结构定义如下:
实践
如上,介绍了 InnoDB 存储的数据格式,接下来实际查看下;本文中在测试的时候,使用的工具可以参考 innodb_ruby 。
----- 新建表
mysql> create table test.foobar (id int);
Query OK, 0 rows affected (0.05 sec)
----- 新建之后,直接查看状态
$ innodb_space -f test/foobar.ibd space-page-type-regions
start end count type
0 0 1 FSP_HDR
1 1 1 IBUF_BITMAP
2 2 1 INODE
3 3 1 INDEX
4 5 2 FREE (ALLOCATED)
此时,只分配了标准的空间页,总共 6 Pages,包括了 FSP_HDR、IBUF_BITMAP、INODE、ROOT INDEX page,而且还有两个已经分配的未使用的页。
参考
关于 InnoDB 的文件格式,详细内容可以参考 Jeremy Cole 的一系列文章 InnoDB internals, structures, and behavior ,对文件存储格式讲解的十分详细。