mysql8.0源码分析——文件管理fil_system

 

 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内存中。

 

 

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值