从上文我们可以看到,InnoDB通过Inode Entry来管理每个Segment占用的数据页,每个segment可以看做一个文件页维护单元。Inode Entry所在的inode page有可能存放满,因此又通过头Page维护了Inode Page链表。
在ibd的第一个Page中还维护了表空间内Extent的FREE、FREE_FRAG、FULL_FRAG三个Extent链表;而每个Inode Entry也维护了对应的FREE、NOT_FULL、FULL三个Extent链表。这些链表之间存在着转换关系,以高效的利用数据文件空间。
当创建一个新的索引时,实际上构建一个新的btree(btr_create),先为非叶子节点Segment分配一个inode entry,再创建root page,并将该segment的位置记录到root page中,然后再分配leaf segment的Inode entry,并记录到root page中。
当删除某个索引后,该索引占用的空间需要能被重新利用起来。
创建Segment
首先每个Segment需要从ibd文件中预留一定的空间(fsp_reserve_free_extents),通常是2个Extent。但如果是新创建的表空间,且当前的文件小于1个Extent时,则只分配2个Page。
当文件空间不足时,需要对文件进行扩展(fsp_try_extend_data_file)。文件的扩展遵循一定的规则:如果当前小于1个Extent,则扩展到1个Extent满;当表空间小于32MB时,每次扩展一个Extent;大于32MB时,每次扩展4个Extent(fsp_get_pages_to_extend_ibd)。
在预留空间后,读取文件头Page并加锁(fsp_get_space_header),然后开始为其分配Inode Entry(fsp_alloc_seg_inode)。首先需要找到一个合适的inode page。
我们知道Inode Page的空间有限,为了管理Inode Page,在文件头存储了两个Inode Page链表,一个链接已经用满的inode page,一个链接尚未用满的inode page。如果当前Inode Page的空间使用完了,就需要再分配一个inode page,并加入到FSP_SEG_INODES_FREE链表上(fsp_alloc_seg_inode_page)。对于独立表空间,通常一个inode page就足够了。
当拿到目标inode page后,从该Page中找到一个空闲(fsp_seg_inode_page_find_free)未使用的slot(空闲表示其不归属任何segment,即FSEG_ID置为0)。
一旦该inode page中的记录用满了,就从FSP_SEG_INODES_FREE链表上转移到FSP_SEG_INODES_FULL链表。
获得inode entry后,递增头page的FSP_SEG_ID,作为当前segment的seg id写入到inode entry中。随后进行一些列的初始化。
在完成inode entry的提取后,就将该inode entry所在inode page的位置及页内偏移量存储到其他某个page内(对于btree就是记录在根节点内,占用10个字节,包含space id, page no, offset)。
Btree的根节点实际上是在创建non-leaf segment时分配的,root page被分配到该segment的frag array的第一个数组元素中。
Segment分配入口函数: fseg_create_general
分配数据页
随着btree数据的增长,我们需要为btree的segment分配新的page。前面我们已经讲过,segment是一个独立的page管理单元,我们需要将从全局获得的数据空间纳入到segment的管理中。
Step 1:空间扩展
当判定插入索引的操作可能引起分裂时,会进行悲观插入(btr_cur_pessimistic_insert),在做实际的分裂操作之前,会先对文件进行扩展,并尝试预留(tree_height / 16 + 3)个Extent,大多数情况下都是3个Extent。
这里有个意外场景:如果当前文件还不超过一个Extent,并且请求的page数小于1/2个Extent时,则如果指定page数,保证有2个可用的空闲Page,或者分配指定的page,而不是以Extent为单位进行分配。
注意这里只是保证有足够的文件空间,避免在btree操作时进行文件Extent。如果在这一步扩展了ibd文件(fsp_try_extend_data_file),新的数据页并未初始化,也未加入到任何的链表中。
在判定是否有足够的空闲Extent时,本身ibd预留的空闲空间也要纳入考虑,对于普通用户表空间是2个Extent + file_size * 1%。这些新扩展的page此时并未进行初始化,也未加入到,在头page的FSP_FREE_LIMIT记录的page no标识了这类未初始化页的范围。
Step 2:为segment分配page
随后进入索引分裂阶段(btr_page_split_and_insert),新page分配的上层调用栈:
btr_page_alloc |--> btr_page_alloc_low |--> fseg_alloc_free_page_general |--> fseg_alloc_free_page_low