201_mysql_innodb_2_表空间_独立表空间_段_区
select @@innodb_file_per_table; #1
独立表空间迁移
#1. 锁定源端t1表
flush tables test.t1 with read lock;
show create table test.t1;
#2. 目标端创建test库和t100w空表
#3. 单独删除空的表空间文件
alter table test.t1 discard tablespace;
# 4. 拷贝源端ibd文件到目标端目录,并设置权限
cp /data/3306/data/test/t1.ibd /data/3307/data/test/
chown -R mysql.mysql /data/*
#5. 导入表空间
mysql> alter table test.t1import tablespace;
# 备注:mysqlutils & 8.0自带命令ibd2sdi 看表结构
mysqlfrm –diagnostic /usr/local/mysql/data/test/new_table.frm #5.7
ibd2sdi city.ibd #8.0
InnoDB 表空间 = 被切分为多个页的池子
一 独立表空间
区(extent) 为了方便管理表空间内多个页,抛出区的概念,
- 一个区extent(1M) = 64个page(16KB)
- 每256个去被分成一组, extent0 ~extent255 是第一组, extent256 ~extent512第二组
- Extent0最开始的三个页面是固定的页面类型:FSP_HDR, IBUF_BITMAP, INODE
FSP_HDR类型:用来登记整个表空间的一些整体属性以及本组所有的区,也就是 extent 0 ~ extent 255区的属性 整个表空间只有一个 FSP_HDR 类型的页面
IBUF_BITMAP类型:这个类型的页面是存储本组所有区的所有页面关于 INSERT BUFFER的信息
INODE类型:这个类型的页面存储了许多称为 INODE 的数据结构
其余各组最开始的2个页面的类型是固定的
XDES类型: extent descriptor,用来登记本组256个区的属性,也就是说对于在 extent 256区中的该类型页面存储的就是extent 256 ~ extent 511 这些区的属性
(FSP_HDR类型的页面其实和 XDES 类型的页面的作用类似,只不过FSP_HDR类型的页面还会额外存储一些表空间的属性)
IBUF_BITMAP类型:存储本组所有区的所有页面关于 INSERT BUFFER的信息
二 段的概念 segment
链表中相邻的两个物理页位置离得非常远,会产生随机 I/O, 尽量让链表中相邻的页的物理位置也相邻(顺序IO)引出的 extent的概念,一次分配64个页,可以连续分配多个页, 消除随机IO
- 扫描范围其实对B+树叶子节点扫描,如果不分叶子/非叶子节点,都放在区中的话,效果会很差
- InnoDB对 B+树的叶子节点喝非叶子节点进行区别对待,也就是说叶子节点/非叶子节点都有自己独有的区
- 存放叶子节点的区的集合算是一个段(segment),存放非叶子节点的区的集合也算是一个段
- 也就是说一个索引会生成两个段,一个叶子节点段,一个非叶子节点段
2.1 申请段过程
- 一般情况下 一张表只有一个聚簇索引(两个段, 叶子/非叶子段)
- 初始化时候 大家都使用 fragment(碎片区) 这些区属于不同的 segment段
- 最开始 从fragment中以单个页面为单位 分配存储空间, 当某个段已经占了32个碎片区的页面, 就会以完整的区来分配
- 段:某些零散的页 以及一些完整的区的集合(叶子节点段/非叶子节点段,回滚段等)
区的4大分类/状态
状态名 |
归属 |
含义 |
FREE |
直属表空间 |
空闲的区, 现在还没用到这个区中的任何页面 |
FREE_FRAG |
直属表空间 |
有剩余空间的碎片区,表示碎片区中还有可用的页面 |
FULL_PAGE |
直属表空间 |
没有剩余空间的碎片区,表示碎片区中的所有页面都被使用,没有空闲页面 |
FSEG |
属于某个段 |
附属于某个段的区, 每一个索引都可以分为叶子节点段和非叶子节点段 InnoDB 还会另外定义一些特殊作用的段,在这些段中的数据量很大时将被使用区作为基本的分配单位 |
2.2 XDES Entry的结构
InnoDB设计XDES Entry的结构(Extent Descriptor Entry),每一个区都对应着一个XDES Entry 结构,这个结构记录了对应的区的一些属性。这个结构的示意图如下
2.2.1 Segment ID (8字节)
每一个段都有一个唯一的编号,用ID表示,此处的 Segment ID字段表示就是该区所在的段。
2.2.2 List Node (12字节)
可以将若干个 XDES Entry 结构串联成一个链表
如果我们想定位表空间内的某一个位置的话,只需指定页号以及该位置在指定页号中的页内偏移量即可。所以:
Pre Node Page Number 和 Pre Node Offset 的组合就是指向前一个 XDES Entry 的指针。
Next Node Page Number 和 Next Node Offset 的组合就是指向后一个 XDES Entry 的指针。
2.2.3 Page State Bitmap (16字节 128个比特位)
一个区默认有64个页,这128个比特位被划分为64个部分,每个部分2个比特位,对应区中的一个页
Page State Bitmap部分的第1和第2个比特位对应着区中的第1个页面,第3/4个比特位对应着区中的第2个页面,
这两个比特位的第一个位表示对应的页是否是空闲的,第二个比特位还没有用
2.2.4 STATE 对应四大状态
2.3 XDES Entry 链表 初衷是减少随机IO且节约空间(数据量小少的浪费空间)
常规流程:
1 查看是否有FREE_FRAG区,如没有申请一个状态为Free的区, 同时更改该去状态 FREE_FRAG,
2 不同段往这个区写数据直到写满(FULL_FRAG)
抛出问题:如何知道区是FREE, FREE_FRAG, FULL_FRAG ?
利用 XDES Entry 中的 List Node的指针做链表
把状态为 FREE 的区对应的 XDES Entry 结构通过 List Node 来连接成一个链表,这个链表称为 FREE 链表(直属表空间)
把状态为 FREE_FRAG 的区对应的 XDES Entry 结构通过 List Node 来连接成一个链表,这个链表称为 FREE_FRAG 链表 (直属表空间)
把状态为 FULL_FRAG 的区对应的 XDES Entry 结构通过 List Node 来连接成一个链表,这个链表称为 FULL_FRAG 链表(直属表空间)
InnoDB为每个段中的区对应的XDES Entry 结构建立了三个链表 (来确认哪些区属于哪个段)
FREE 链表:同一个段中,所有页面都是空闲的区对应的 XDES Entry 结构会被加入到这个链表。注意和直属于表空间的 FREE 链表区别开了,此处的 FREE 链表是附属于某个段的。
NOT_NULL 链表:同一个段中,仍有空闲空间的区对应的 XDES Entry 结构会被加入到这个链表中
FULL 链表:同一个段中,已经没有空闲空间的区对应的 XDES Entry 结构会被加入到这个链表
综上 每一个索引都对应两个段,每个段都会维护上述的3个链表
create table tt (
c1 int not null auto_increment,
c2 varchar(100),
c3 varchar(100),
primary key (c1),
key idx_c2(c2)
)ENGINE=InnoDB;
这个表共有两个索引,一个聚簇索引,一个二级索引,
所以这个表共有4个段,每个段都会维护上述3个链表,总共12个链表,
加上直属于表空间的3个链表,
整个独立表空间需要维护15个链表
所以段在数据量比较大时插入数据的话,会先获取 NOT_NULL 链表的头节点,直接把数据插入这个头节点对应的区中即可,
如果该区的空间已经被用完,就把该节点移到 FULL 链表中
三 链表基节点
抛出问题,15个链表,如何找到某个链表的头/尾节点? Innodb设计 链表基节点 (List Base Node) 每个链表都有此基节点
List Length 表明该链表一共有多少节点
First Node Page Number 和 First Node Offset 表明该链表的头节点在表空间中的位置。
Last Node Page Number 和 Last Node Offset 表明该链表的尾节点在表空间中的位置。
链表小结
表空间是由若干个区组成的,每个区都对应一个 XDES Entry 的结构,
直属于表空间的区对应的 XDES Entry 结构可以分成 FREE、FREE_FRAG 和 FULL_FRAG 这3个链表;
每个段可以附属若干个区,每个段中的区对应的 XDES Entry结构可以分成 FREE、NOT_NULL 和 FULL 这3个链表。
每个链表都对应一个 List Base Node 结构,这个结构里记录了链表的头、尾节点的位置以及该链表中包含的节点数。
正是因为这些链表的存在,管理这些区才变得容易
四 段的结构
段其实不对应表空间中某一个连续的物理区域,而是一个逻辑上的概念由若干个零散的页面以及一些完整的区组成。
像每个区都有对应的 XDES Entry 来记录这个区中的属性一样,InnoDB的设计者为每个段都定义一个 INODE Entry 结构来记录段中的数据
4.1 Segment ID
就是指这个 INODE Entry 结构对应的段的编号(ID)。
4.2 NOT_FULL_N_USED
这个字段指的是 NOT_FULL 链表中已经使用了多少个页面
4.3 3个 List Base Node
段的FREE 链表 ,NOT_FULL 链表,FULL 链表定义了 List Base Node,
这样想查找某个段的某个链表的头节点和尾节点的时候,就可以直接到这个部分找到对应链表的 List Base Node
4.4 Magic Number
这个值是用来标记这个 INDEO Entry 是否已经被初始化了(初始化的意思就是把各个字段的值都填进去了)。如果这个数字是 97937874,表明该 INODE Entry 已经初始化,否则没有被初始化
4.5 Fragment Array Entry
前面强调过段是一些零散页面和一些完整的区的集合,每个Fragment Array Entry 结构都对应着一个零散的页面,这个结构一共4个字节,表示一个零散页面的页号
五 各类型页面详细情况
5.1 FSP_HDR 类型
第一个组的第一个页面,当然也是表空间的第一个页面,页号为0。这个页面的类型是 FSP_HDR,它存储了表空间的一些整体属性以及第一个组内256个区的对应的 XDES Entry 结构
5部分组成
名称 |
中文名 |
占用空间大小 |
简单描述 |
File Header |
文件头部 |
38字节 |
页的一些通用信息 |
File Space Header |
表空间头部 |
112字节 |
表空间的一些整体属性信息 |
XDES Entry |
区描述信息 |
10240字节 |
存储本组256个区对应的属性信息 |
Empty Space |
尚未使用的空间 |
5986字节 |
用于页结构的填充,没啥实际意义 |
File Trailer |
文件尾部 |
8字节 |
校验页是否完整 |
5.1.1 File Space Header用来存储表空间的一些整体属性的
名称 |
占用空间大小 |
描述 |
Space ID |
4字节 |
表空间的 ID |
Not Used |
4字节 |
这4个字节未被使用,可以忽略 |
Size |
4字节 |
当前表空间占用的页面数 |
FREE Limit |
4字节 |
尚未被初始化的最小页号,大于或等于这个页号的区对应的 XDES Entry 结构都没有被加入 FREE 链表 |
Space Flags |
4字节 |
表空间一些占用存储空间比较小的属性 (表空间中与布尔类型相关的属性) |
FRAG_N_USED |
4字节 |
FREE_FRAG 链表中已使用的页面数量 |
List Base Node for FREE List |
16字节 |
FREE 链表的基节点 |
List Base Node For FREE_FRAG List |
16字节 |
FREE_FRAG 链接的基节点 |
List Base Node For FULL_FRAG List |
16字节 |
FULL_FRAG 链表的基节点 |
Next Unused Segment ID |
8字节 |
当前表空间中下一个未使用的 Segment ID (类似全局变量, 一个索引2个段, 每个段ID 唯一) |
List Base Node for SEG_INODES_FULL List |
16字节 |
SEG_INODES_FULL 链表的基节点 每个段对应一个INODES_Entry结构, 这些INODES_Entry会集中存放在类型为INODE的页中, 如果很多就需要多个INODE页-> 形成SEG_INODES_FULL 链表—已经被INODES_Entry填满 无法存放过多的INODE Entry了 |
List Base Node for SEG_INODES_FREE List |
16字节 |
SEG_INODES_FREE 链表的基节点 |
5.1.2 XDES Entry 链表 (XDES Entry结构对应表空间一个区)
XDES Entry 就是在表空间的第一个页面中保存的。一个XDES Entry结构的大小是40字节,但是一个页面的大小有限,只能存放有限个XDES Entry结构,所以才把256个区划分成一组,在每组的第一个页面中存放256个XDES Entry结构。因为每个区对应的XDES Entry结构的地址是固定的,所以访问这些结构的时候就比较方便
5.1.3 XDES 类型 (XDES 用来登记本组256个区的属性)
每个组的第一个页面只需要记录本组内所有的区对应的XDES Entry结构,不需要再记录表空间的属性了,为了和 FSP_HDR 类型做区别,把之后的每个分组的第一个页面的类型定义为 XDES,它的结构和 FSP_HDR类似
5.1.4 INODE 类型
第一个分组的第三个页面的类型是 INODE
InnoDB为每个索引定义了两个段,而且为某些特殊功能定义了些特殊的段,为每个段设计了一个 INODE Entry 结构,这个结构中记录了关于这个段的相关属性。
而INODE类型的页就是为了存储 INODE Entry 结构而存在的
一个INODE 类型的页面是有这几部分组成的
名称 |
中文名 |
占用空间大小 |
简单描述 |
File Header |
文件头部 |
38字节 |
页的一些通用信息 |
List Node For INODE Page List |
通用链表节点 |
12字节 |
存储上一个INODE页面和下一个INODE页面的指针 |
INODE Entry |
段描述信息 |
16320字节 |
具体的INODE Entry 结构 |
Empty Space |
尚未使用空间 |
6字节 |
用于页结构的填充,没啥实际意义 |
File Trailer |
文件尾部 |
8字节 |
校验页是否完整 |
List Node For INODE Page List
因为一个表空间中可能存在超过85个段,所以可能一个 INODE 类型的页面不足以存储所有的段对应的 INODE Entry 结构,所以就需要额外的 INODE 类型的页面来存储这些结构。为了方便管理这些 INODE 类型的页面,InnoDB将这些 INODE 类型的页面串联成两个不同的链表:
SEG_INODES_FULL 链表:该链表中的 INODE 类型的页面中已经没有空闲空间来存储额外的 INODE Entry 结构了。
SEG_INODES_FREE 链表:该链表中的 INODE 类型的页面中还有空闲空间来存储额外的 INODE Entry 结构。
这两个链表的基节点就存储在 File Space Header里面, 也就是说这两个链接的基节点的位置是固定的,所以访问起来很方便。以后每当新创建一个段(创建索引时就会创建段)时,都会创建一个 INODE Entry 结构与之对应,存储 INODE Entry 的大致过程如下:
先看SEG_INODES_FREE链表是否为空,如果不为空,直接从该链表中获取一个节点,也就相当于获取一个仍有空闲空间的 INDEO 类型的页面,然后把该 INODE Entry 结构放到该页面中。当该页面中无剩余空间时,就把该页放到SEG_INODES_FULL链表中。
如果SEG_INODES_FREE链表为空,则需要从表空间的 FREE_FRAG 链表中申请一个页面,修改该页面的类型为 INODE,把该页面放到 SEG_INODES_FREE 链表中,与此同时把该 INODE Entry 结构放入该页面
Segment Header 结构的运用
一个索引会产生两个段,分别是叶子节点段和非叶子节点段,而每个段都会对应一个 INODE Entry 结构,我们要想知道某个段对应哪个INODE Entry 结构,就需要找个地方记下来这个对应关系 INDEX 类型的页里的 Page Header 部分
名称 |
占用空间大小 |
描述 |
PAGE_BTR_SEG_LEAF |
10字节 |
B+树叶子段的头部信息,仅在 B+树的根页定义 |
PAGE_BTR_SEG_TOP |
10字节 |
B+树非叶子段的头部信息,仅在 B+树的根页定义 |
PAGE_BTR_SEG_LEAF和PAGE_BTR_SEG_TOP都占用10个字节,它们其实对应一个叫 Segment Header 的结构,该结构示意图如下
名称 |
占用字节数 |
描述 |
Space ID of the INODE Entry |
4 |
INODE Entry 结构所在的表空间 ID |
Page Number of the INODE Entry |
4 |
INODE Entry结构所在的页面页号 |
Byte Offset of the INODE Entry |
2 |
INODE Entry 结构在该页面中的偏移量 |
PAGE_BTR_SEG_LEAF记录着叶子节点段对应的 INODE Entry 结构的地址是哪个表空间的哪个页面的哪个偏移量,
PAGE_BTR_SEG_TOP记录着非叶子节点段对应的 INODE Entry 结构的地址是哪个表空间的哪个页面的哪个偏移量。
这样子索引和其对应的段的关系就建立起来了。不过需要注意的一点是,因为一个索引只对应两个段,所以只需要在索引的根页面中记录这两个结构即可
- 点赞
- 收藏
- 关注作者
评论(0)