fil_system
数据结构
fil_system是在内存中维护的表文件/表空间信息,对应的数据结构是Fil_system,其对象全局只有一个: fil_system,与其相关的数据结构有Fil_shard、fil_space_t和fil_node_t。数据结构如下:
class Fil_system {
...
Fil_shards m_shards; //Fil_shards 管理器,默认有64个
const size_t m_max_n_open; //允许打开的最大文件数 默认1024
space_id_t m_max_assigned_id; //当前系统最大表空间id,启动时打开space时也会赋值,创建新表时会赋值
bool m_space_id_reuse_warned; //space id重用告警
dd_fil::Tablespaces m_moved; //用于存放space id 不变但文件路径改变的文件,在innobase_dict_recover的boot_tablespaces流程构建,并在fil_open_for_business流程更新fil_system
Tablespace_dirs m_dirs; //表空间目录路径, 在innobase_post_recover阶段会清空ibd、undo目录路径
std::vector<std::string> m_old_paths; //5.7升级使用
...
}
class Fil_shard {
...
using File_list = UT_LIST_BASE_NODE_T(fil_node_t);
using Space_list = UT_LIST_BASE_NODE_T(fil_space_t);
using Spaces = std::unordered_map<space_id_t, fil_space_t *>;
...
const size_t m_id;
Spaces m_spaces;
Names m_names;
File_list m_LRU;
Space_list m_unflushed_spaces;
int64_t m_modification_counter;
static std::atomic_size_t s_n_open;
static std::atomic_size_t s_open_slot;
...
}
struct fil_space_t {
...
using Files = std::vector<fil_node_t, ut_allocator<fil_node_t>>;
Files files;
...
}
struct fil_node_t {
...
fil_space_t *space;
char *name;
bool is_open;
pfs_os_file_t handle;
...
}
其中fil_space_t 代表一个表空间(tablespace),fil_node_t 代表某个表空间中的一个文件,可以是表数据、日志数据、临时文件,用于对文件的读写操作。
下文主要从奔溃恢复的流程分析fil_system加载管理底层数据文件,仅作为学习记录。
启动流程
1. innobase_init_files、srv_start
srv_start阶段与fil_system相关的函数调用堆栈如下:
| srv_start
| |==> 崩溃恢复前准备
| |==> fil_init //初始化fil_system, 默认最多打开1024个文件,m_shards大小为64
| |==> fil_set_scan_dir //设置文件扫描最上层目录 ,/mnt/mysql/data/
| |==> fil_scan_for_tablespaces //扫描所有ibd及undo文件 加入m_dirs中
| | |==> Fil_system::scan
| | | |==> Tablespace_dirs::scan
| | | | |==> fil_get_scan_threads //根据ibd文件数决定扫描线程数目
| | | | |==> Tablespace_dirs::duplicate_check
| | | | | |==> //根据文件名读取space_id
| | | | | |==> Fil_system::get_tablespace_id(phy_filename)
| | | | | |==> Tablespace_files::add(space_id, filename)
| |==> ...
| |==> srv_sys_space.open_or_create //打开系统表空间ibdata1,并获取flushed_lsn
| | |==> read_lsn_and_check_flags
| | | |==> read_first_page
| | | |==> //将dblwr加载到recv_sys->dblwr内存中,如果ibdata日志损坏,则通过dblwr恢复
| | | |==> recv_sys->dblwr->load
| | | |==> validate_first_page //校验第一个页是否正常,并读取flushed_lsn
| | | | |==> mach_read_from_8 //读取LSN,偏移为FIL_PAGE_FILE_FLUSH_LSN
| | | |==> restore_from_doublewrite //如果第一个页异常,则从dblwr拷贝数据到0号页
| | |==> fil_space_create // 创建fil_space
| | | |==> fil_system->shard_by_id //根据 space id 获取 shard
| | | |==> shard->space_create //创建fil_space,加入到Fil_shard 的hash table中(m_spaces)
| | | |==> fil_space_t::s_sys_space = space //设置全局变量
| | | |==> fil_space_t::s_redo_space = space //设置全局变量
| | |==> fil_node_create //创建fil_node,加入到m_shards[0]->m_spaces[0]->files中
| |==> ...
| |==> log_space = fil_space_create //为redo log创建fil_space 加入到fil_system内存中,m_shards[63]
| |==> fil_node_create //为每个redo log文件创建fil_node_t,加载到m_shards[63]-》m_spaces->files中,此时文件都未打开
| |==> fil_open_log_and_system_tablespace_files //遍历m_shards 打开 shard->m_spaces->files的所有文件
| | |==> fil_system->open_all_system_tablespaces
| | | |==> shard->open_all_system_tablespaces
| |==> ...
| |==> //奔溃恢复阶段打开需要恢复的物理文件
| |==> recv_apply_hashed_log_recs
| | |--> fil_tablespace_open_for_recovery
| | | |--> Fil_system::open_for_recovery
| | | | |--> Fil_system::lookup_for_recovery
| | | | | |--> get_scanned_files //根据space id 从m_dirs中查找,如果找不到,则返回false
| | | | |--> get_scanned_files
| | | | |--> //如果是活跃的Undo表空间,则返回
| | | | |--> Fil_system::ibd_open_for_recovery
| | | | | |--> 如果已经加载到cache,do nothing
| | | | | |--> Fil_shard::ibd_open_for_recovery
| | | | | | |--> //打开文件后,开始校验
| | | | | | |--> Datafile::validate_for_recovery
| | | | | | | |--> //校验第一页
| | | | | | | |--> Datafile::validate_first_page
| | | | | | | | |--> Datafile::read_first_page//读取第一页,然后检验
| | | | | | | | |--> //读取之前的绝对路径,并与现在的对比
| | | | | | | | |--> fil_space_read_name_and_filepath
| | | | | | | |--> //if DB_CORRUPTION
| | | | | | | |--> open_read_write
| | | | | | | |--> find_space_id
| | | | | | | |--> //如果有异常,则从dblwr恢复
| | | | | | | |--> restore_from_doublewrite
| | | | | | | | |--> //copy the page from double write buffer to ibd
| | | | | | | | |--> os_file_write
| | | | | | | |--> //Free previously read first page and then re-validate
| | | | | | | |--> free_first_page
| | | | | | | |--> Datafile::validate_first_page //再次检查
| | | | | | | |--> //end if DB_CORRUPTION
| | | | | | |--> Fil_shard::space_create
| | | | | | |--> create_node /*Attach a file to a tablespace*/
| | | | | | |-->
| | | | |--> buf_dblwr_recover_pages //从double write恢复此表所有页面
| | | | | |--> buf_dblwr_recover_page
| | | | | |--> fil_flush_file_spaces
| | |--> fil_tablespace_lookup_for_recovery
| | | |--> Fil_system::lookup_for_recovery
| |==> ...
| |==> //打开undo 表空间 m_shard[60] m_shards[61]
| |==>srv_undo_tablespaces_init
| | |-->srv_undo_tablespace_open
| | | |--> fil_space_create
| | | |--> fil_node_create
| | | |--> fil_space_open
| |==> srv_open_tmp_tablespace //打开temp_1.ibt~temp_10.ibt, 存放在m_shards[13]
| | |==> SysTablespace::open_or_create
| | |==> fil_space_open
| |==> ibt::open_or_create //打开ibtmp1, 存放在m_shards[7] ~m_shards[16]
|==> innobase_init_files
| |==> srv_start //启动innodb 引擎
| |==> dd_create_hardcoded/dd_open_hardcoded //创建或打开DD表空间,加入到fil_system中
| | |==> fil_ibd_open //打开mysql.ibd 文件,存放在m_shards[14]
srv_start 阶段与fil_system相关的步骤如下:
- 初始化fil_system
- 设置fil_system 扫描文件的绝对路径
- 扫描绝对路径下的文件,将ibd、undo 文件加入到fil_system->m_dirs中
- 将ibdata1加入到fil_system内存中,此时文件未打开
- 将redo log加入到fil_system内存中,此时文件未打开
- fil_system打开ibdata1 、redo log文件
- 奔溃恢复阶段打开需要恢复的ibd文件
- fil_system 打开 undo 文件
- fil_system打开 ibtmp1、以及session临时表空间'#innodb_temp'下的10个临时文件temp_1.ibt~temp_10.ibt
在InnoDB启动时,首先会通过fil_set_scan_dir设置数据文件绝对目录。然后进入fil_scan_for_tablespaces,并行扫描datadir这个目录下scan所有的ibd、undo文件,读取每个文件的0号Page,解析获得文件对应的space id,检查是否存在相同space id但文件名不同的文件,并且和文件名也就是Tablespace名做一个映射,保存在Fil_system的Tablespace_dirs midrs中,这个mdirs主要用来在InnoDB的crash recovery阶段解析log record时,通过space_id拿到文件名。
在InnoDB运行过程中,在内存中会保存所有Tablesapce的Space_id,Space_name以及相应的”.ibd”文件的映射,这个结构都存储在InnoDB的Fil_system这个对象中,在Fil_system这个对象中又包含64个shard,每个shard又会管理多个Tablespace,整体的关系为:Fil_system -> shard -> Tablespace。InnoDB并不是在启动的时候就打开所有的用户表空间文件,而是在redo log回放过程中按需打开用户表空间文件(recv_apply_hashed_log_recs->fil_tablespace_open_for_recovery)。
数据库启动后,InnoDB 会通过srv_sys_space.open_or_create 函数读取系统表空间中 flushed_lsn ,这一个 LSN 只在系统表空间的第一个页中存在,而且只有在正常关闭的时候写入。若系统表空间0号页异常,需要从dblwr中拷贝数据到系统表空间的0号页。此阶段仅仅是为ibdata1创建fil_space_t、fil_node_t内存结构,并加入到fil_system中,系统文件还未打开。其中fil_system->m_shards 与space id的关系,参考函数Fil_system::shard_by_id,主要规则如下:
- redo log 存放在m_shards[63]
- undo log存放在
- 其他 shard_id = space_id % 58, 系统表空间存放在m_shard[0]中
2. innobase_dict_recover
在奔溃恢复完成后,在dd事务、动态元数据回滚前,需要把DD中所保存的Tablesapce全部进行validate check一遍(Validate_files::check->fil_ibd_open->validate_to_dd),用于检查是否有丢失ibd文件或者数据有残缺等情况,在这个过程中,会把所有保存在DD中的表空间信息保存在Fil_system中,并打开所有未打开的表文件。文件检查过程中若发现文件路径改变了,需要将文件信息加入到fil_system->m_moved中,后继流程会调用函数fil_open_for_business更新fil_system内存中的这些文件
|==> DDSE_dict_recover
| |==> innobase_dict_recover
| | |==> boot_tablespaces
| | | |==> Validate_files::validate
| | | |==> //multi thread scan
| | | |==> n_threads=fil_get_scan_threads
| | | |==> Validate_files::check
| | | | |==> fil_add_moved_space //文件路径改变,将文件信息加入到fil_system->m_moved中
| | | | |==> fil_ibd_open
| | | | | |==> if(validate)
| | | | | |==> Datafile::validate_to_dd
| | | | | | |==> /*校验第一页是否正常,并读取flushed_lsn*/
| | | | | | |==> Datafile::validate_first_page
| | | | | | | |==> //读取第一页,然后检验
| | | | | | | |==> Datafile::read_first_page
| | | | | | | | |==> os_file_read_no_error_handling
| | | | | | | | | |==> os_file_read_no_error_handling_func
| | | | | | | | | | |==> os_file_read_page
| | | | | | | |==> //读取之前绝对路径,并与现在的对比
| | | | | | | |==> fil_space_read_name_and_filepath
| | | | | |==> end if (validate)
| | | | | |==> fil_space_create
| | | | | |==> Fil_shard::create_node /*Attach a file to a tablespace*/
| | |==> ...
| | |==> fil_open_for_business //根据fil_system-》m_moved更新fil_system
3. innobase_post_recover
该阶段操作很简单就是清空fil_system->m_dirs中ib、undo文件路径map。
总结
总结下奔溃恢复前,奔溃时,以及奔溃恢复后,innodb全局变量fil_system对ibd、redo、undo文件的处理
- 奔溃恢复前,在InnoDB启动时,会先从datadir这个目录下scan所有的.ibd、undo文件,并且解析其中的Page0,读取对应的Space_id,检查是否存在相同Space_ID但文件名不同的文件,将文件名与路径作为一个映射保存在midrs中。打开系统表空间、redo表空间并加载到fil_system内存中
- 奔溃恢复时,按需调用fil_tablespace_open_for_recovery,打开相应用户ibd文件进行恢复
- 奔溃恢复后事务回滚阶段之前,fil_system打开undo文件,创建打开ibtmp1、.ibt文件
- 奔溃恢复结束后事务回滚阶段,在boot_tablespaces函数中,会把DD中所保存的表空间全部进行validate check一遍,用于检查是否有丢失ibd文件或者数据有残缺等情况,在这个过程中,会把DD中的表空间信息,且在crash recovery中未能open的表空间全部打开一遍,并保存在Fil_system中,至此整个InnoDB中所有的表空间都加载到fil_system内存中。