相关文件: fil0fil.h fil0fil.c
功能:对disk上的表空间及组成表空间的物理文件进行管理(如新建,打开,关闭,删除,重命名等操作);对表空间中的页在物理文件上进行存取(IO操作)。
Introduction
表空间的物理组成
Innodb在对数据库文件的管理上使用了类似oracle的表空间(tablespace)技术。表空间只是逻辑上的管理方法,数据库的存储在物理上仍是按文件进行。在innodb中有三种表空间:系统表空间(也被称为共享表空间),重做日志表空间和独立表空间。这三种表空间在物理上究竟分别指的是什么呢?先来看下msyqld启动时与这三种表空间相关的参数:
innodb_data_file_path |
该参数与系统表空间相关:比如设置其为 innodb_data_file_path=ibdata1:2G; ibdata2:2G: ibdata3:2G:autoextend则系统表空间在物理上就由ibdata1, ibdata2, ibdata3这三个文件组成,三个文件在新建时都为1GB大小,在以后的时间里前两个文件大小一直固定不变,最后一个文件因有autoextend可以自增长。 Note:系统表空间中的文件可以设置成不在MySQL data directory中,可以使它们分布在不同的disk以均衡负载。 |
innodb_log_files_in_group innodb_log_file_size |
这两个参数与重做日志表空间相关: 比如设置innodb_log_files_in_group=3 则重做日志表空间在物理上由ib_logfile0, ib_logfile1, ib_logfile2共3个文件组成,每个重做日志文件的大小都相同,由innodb_log_file_size参数指定每个文件的大小 |
innodb_file_per_table |
该参数与独立表空间相关:只有启用该参数才使用独立表空间;当启用了该参数后,创建的每个innodb表都自成一个表空间,称为独立表空间,这个表空间在物理上只会由一个文件组成,文件名为database_name/table_name.ibd Note: 本篇文章的解读是假设开启了独立表空间功能 |
三种表空间与物理文件的关系示意图:
总结:表空间在物理是由一到多个物理文件组成的,而且独立表空间有且只有一个物理文件,系统表空间与重做日志表空间根据对相应参数的设置可以有一个或者多个物理文件。
表空间的逻辑组成
上一节说明了表空间的物理组成,而表空间在逻辑上是由一系列的连续编号的页组成。示意图如下:
上图表示了一个含有n个页的表空间,每一个页都有一个page_no(也叫做页偏移),page_no从0开始顺序递增。对于非压缩页,每个page的大小为16KB。
疑点解释:与表空间和页相关的另外两个概念是段(segment)和区(extent),一种惯用说法是说表空间由多个segment组成,一个segment由多个extent组成,而1个extent又是由64个页组成。而在上边的说明中已指出表空间在逻辑上是由连续的页组成的,那么segment和extent又是从何而来呢?这就涉及到表空间的管理问题了,在innodb源代码中这一块是由fsp模块负责的,对于该模块这里不深入解读,此处需要明确的就是表空本质上只是由连续编码的页组成,只是innodb中为了管理以及性能上的需要,页不只是数据页和索引页,还有很多种其他页类型,其中某些页类型就专门用于存储segment或extent信息,读取这些页就可以知道这个segment或extent控制的页范围等信息,从而引申出了segment和extent的概念。想要了解表空间对segment,extent和page的管理需要去解读fsp模块,这将放在以后进行解读,本篇文章将专注于解读fil模块。
表空间的物理组成与逻辑组成间的关系--------文件与页的关系
至此已经说明了表空间的物理组成与逻辑组成,那么表空间的物理组成与逻辑组成间又是什么样的关系呢?也就是说物理文件与页之间是什么关系呢?它们间的关系当然是页是存储在物理文件上啦,只是对于独立表空间而言,所有的页都存储在一个文件上,而对于系统表空间和重做日志表空间而言,如果它们包含多个物理文件的话,所有的页会分布在不同的文件上。示意图如下:
需要注意的是在系统表空间和重做日志表空间里,一个页不能跨越一个文件的末尾和随后一个文件的开始,也即每个页必须完整地存储在某个文件中。如果指定的这两个表空间的物理文件的大小不为page的整数倍时,每一个物理文件都会按page大小进行截取,最后多余的部分不被使用(由于在设置这二个表空间的物理文件大小时通常都会以M或者G为单位,其必为page的整数倍,所以截取文件这种操作并不会发生)。
Note: innodb中的表空间支持压缩功能,但是只有独立表空间可以被压缩。对于非压缩page,其大小为16KB,一个extent就为1MB。因而对于系统表空间和重做日志表空间来说,其页大小始终是16KB,对独立表空间来说,页大小要看其是否采用了压缩以及何中压缩级别。
fil模块数据结构解读
上边Introduction部分所说的表空间都是位于disk上,Innodb源代码中与其紧密相关的模块之一是fil模块。fil模块的主要功能是:对disk上的表空间及组成表空间的物理文件进行管理(如新建,打开,关闭,删除,重命名等操作);对表空间中的页在物理文件上进行存取(IO操作)。
要实现fil模块所负责的上述功能,就需要在内存中创建一套用于管理disk上表空间的数据结构。 fil模块为实现这个目的使用了三个数据结构:fil_space_t , fil_node_t 和fil_system_t (fil_space_t类型实例用space表示, fil_node_t实例用fil_node表示,fil_system_t实例用fil_system表示 )。space用于管理打开的对应表空间,fil_node用于管理对应的物理文件,fil_system用于管理所有打开的表空间。
Introduction部分提到的三种表空间类型在fil模块源代码里被统称为space,并且被划分成两种类型:FIL_TABLESPACE和FIL_LOG。系统表空间和独立表空间就属于FIL_TABLESPACE类型,而重做日志表空间就属于FIL_LOG类型了。在fil_space_t类型中有一个unsigned long类型的成员名为id,表示每个打开的space的编号,编号0始终表示系统表空间,重做日志表空间的space id必须大于或者等于SRV_LOG_SPACE_FIRST_ID(0xFFFFFFF0UL),但是目前版本的innodb中只使用了一组重做日志组,其space id就为SRV_LOG_SPACE_FIRST_ID。 而独立表空间的id则在(0, SRV_LOG_SPACE_FIRST_ID)之间。id可能的最大取值为0xFFFFFFFFUL(上层通过32位位域对unsigned long类型限定的结果)。
fil_system对所有打开的表空间是通过space_list双向链表来管理的,其中第一个节点必为系统表空间对应的space,第二个节点必为重做日志表空间对应的space。这两个表空间在服务器启动后直到服务器关闭一直存在且在双向链表里始终依次占据前边两个位置。独立表空间在被创建或打开后对应的space就被创建并依次加入到该双向链表的末尾。
对于每一个打开的space,它所管理的所有物理文件在内存中的控制节点fil_node也会被创建。并且space通过chain双向链表将这些fil_node进行链接。也就是说space和它控制的所有fil_node是同生同灭的。此外,对于系统表空间和重做日志表空间而言,它们的所有物理文件在服务器启动好后到服务器关闭整个时间里都是出于打开状态的。但是对于独立表空间来说,即使space和那个唯一的fil_node在内存中被建立,但是fil_node所控制的文件也可能处于关闭状态,这种情况可出现于新建一个innodb类型的表但又未在随后使用该表的情况。
fil_system有一个成员是双向链表头结点LRU,用于链接打开的某些物理文件。那么为什么要使用LRU,另外哪些文件又会被链接到LRU双向链表中呢?由于操作系统的限制,一个进程能够同时打开的文件数是有限制的,比如在linux系统上使用uname -n会得到结果1024,表示一个进程同时最多只能打开1024个文件。在mysqld启动时可以使用参数innodb_ open_files来设置innodb可以打开的最大文件数(设置时不要超过OS的限制),默认值为300。innodb_ open_files的值会被赋值给fil_system的成员max_n_open,max_n_open就表示innodb能够同时打开的文件限制数目,另外fil_system中有个成员为n_open,用来记录当前已打开的物理文件数目。由于有max_n_open的限制,当有一个表数目非常多的数据库且开启了独立表空间功能时,就必定不能同时打开存在的所有文件了。为了解决这个问题,就使用了LRU策略:将所有已打开且当且没有IO操作行将进行的独立表空间的物理文件挂在LRU链表上,当已打开的文件数目达到限制后,要打开其他独立表空间文件就需要进行置换,位于LRU尾部文件的将被首先关闭,然后新打开的独立表空间文件被加入LRU头部。
小结:LRU链表上链接的fil_node节点必须满足下列三个条件: (1) fil_node控制的文件已打开,即fil_node->open为 TRUE; (2) fil_node控制的文件没有IO操作即将进行,即fil_node->n_pending需要为0; (3) fil_node控制的文件必须是独立表空间文件,因为系统表空间和重做日志表空间的物理文件需要一直被打开,因此不能被挂在LRU上。
为了高效地查找表空间,fil_system中使用了两个哈希表,一个是按space->id进行查找,一个是按space->name进行查找,前者最常用,对应的函数是fil_space_get_by_id。
fil_system还有个成员unflushed-space,将待刷新的所有space用双向链表链接起来,以用于同一刷新。fil_system中的成员mutex用于保证线程互斥访问fil_system、fil_node、space所组成的整套套数据结构。由于DDL涉及的并发性并不高,所以只是用一个mutex来保证整套数据结构的互斥访问性能影响不大。
数据流程
上边已将fil模块下涉及的数据结构做了主要说明,但fil_system、fil_node、space都还有很多其他数据成员上边没有介绍,具体信息去查看相应的源代码。下边将从数据流的角度来说明innodb在启动、运行、关闭时如何与fil模块进行交互的。
innodb在启动时会调用innobase_start_or_create_for_mysql函数,在该函数里有五个与fil模块紧密相关的的函数会被依次调用:fil_init,open_or_create_data_files, open_or_create_log_file, fil_open_log_and_system_tablespace_files和dict_check_tablespaces_and_store_max_id。fil_init函数用于创建fil_system数据结构。open_or_create_data_files与系统表空间相关:它首先尝试去创建innodb_data_file_path中指明的物理文件,如果是装好mysql服务器后第一次启动mysqld,则该创建是成功的,但是当物理文件已经存在于disk上时,则创建会失败,open_or_create_data_files函数就打开innodb_data_file_path中指明的文件,检查文件的大小与innodb_data_file_path中指明的是否一样,若一切成功,则关闭创建或者打开的文件,然后调用fil_space_create为系统表空间创建space,调用fil_node_create为所有innodb_data_file_path中指明的文件创建fil_node。open_or_create_log_file函数与open_or_create_data_files的操作过程相似,只是它是针对于重做日志表空间。上一节说过系统表空间和重做日志表空间的物理文件在innodb启动好后始终都是打开的,因此这里就调用了fil_open_log_and_system_tablespace_files函数来打开这些物理文件。然后innobase_start_or_create_for_mysql函数会调用dict_check_tablespaces_and_store_max_id函数,dict_check_tablespaces_and_store_max_id函数的作用是扫描data dictionary(所有.ibd文件的表空间id,表空间name等信息都会存储在这里),得到独立表空间的space id和space name,然后调用fil_open_single_table_tablespace函数来为独立表空间创建space和fil_node。至此innodb服务器就启动好了,创建好后的数据结构的示意图如下图所示(这里忽略了哈希表及其他一些数据成员)。要注意的是innodb启动后所有表空间的space和所有物理文件的fil_node都会被创建,但是独立表空间的.ibd 文件没是处于关闭状态的,所有最初LRU也是为空的。
那么.ibd文件在什么时候才被打开呢?这就要提到fil模块下的fil_io函数。fil_io函数是一个非常重要的函数,在很多地方都需要调用该函数(比如向buffer pool中读取页),它在fil模块中负责对表空间中的页在物理文件上进行存取。当fil_io函数被调用来对独立表空间进行IO操作时,会根据space_id会去查看对应的.ibd文件是否打开,若没有打开则会首先使用fil_node_open_file去打开.ibd文件并挂在LRU首部。然后fil_node_prepare_for_io函数又会将其从LRU中取下,以准备进行IO操作;当IO操作完成后,fil_node_complete_io会被调用并将.ibd文件再次挂到LRU首部。
在innodb运行过程中,若进行innodb类型表的DDL操作时,也会调用到fil模块中相应的函数。CREATE一个innodb表时会调用到fil_create_new_single_table_tablespace函数,DELETE一个innodb表时会调用到fil_delete_tablespace,RENAME一个innodb表时会调用到fil_rename_tablespace函数。fil_create_new_single_table_tablespace首先会去创建.ibd文件,然后关闭该.ibd文件,再在内存中创建相应的space和fil_node。fil_rename_tablespace函数首先需要关闭相应的.ibd文件,然后根据space_id找到space,对该space的space->name和相应的fil_node->name进行重命名,最后再对.ibd文件重命名,但是重命名后的.ibd文件不再打开。fil_delete_tablespace函数首先根据space_id找到space,然后销毁space和相应的fil_node,最后再删除.ibd文件。
在innodb关闭时,innobase_shutdown_for_mysql函数会先后调用logs_empty_and_mark_files_at_shutdown和fil_close函数。logs_empty_and_mark_files_at_shutdown函数会调用到fil模块的fil_close_all_files函数,fil_close_all_files会从头开始遍历fil_system->space_list链表,然后关闭每个space上的文件,再销毁space和space控制的所有fil_node。所以fil_close_all_files函数执行完后Fig.4中的数据结构就只剩下fil_system了,销毁fil_system就是通过调用fil_close函数来完成了。