简要记录跟踪代码,很多代码流程没有细细的跟进去,只是了解了个大概,杂七杂八,还有太多不了解的地方。
不过,一知半解总比一无所知要好点…sign…
////////////////////////////////////////////
一、innobase_init
1.初始化存储引擎接口函数、检查指定的page大小(innodb_page_size,Percona版本支持16k以下的page size定义)、innodb_log_block_size。
2.检查是否通过记录在innodb层的relay log信息更新relay-info文件(通过innodb_recovery_update_relay_log来控制)
3.innodb文件路径、类型、各类全局变量等信息初始化,
4.innobase_start_or_create_for_mysql //主要函数,稍后细述
5.更新每个bp实例的buf_pool->LRU_old_ratio(innodb_old_blocks_pct),也就是LRU上old list的百分比。
6.初始化innobase_share_mutex、prepare_commit_mutex、commit_threads_m、commit_cond_m、commit_cond等变量。
可以看到在这个函数中除了innobase_start_or_create_for_mysql这个主要函数外,基本上都是些变量之类的初始化,我们简要看看innobase_start_or_create_for_mysql主要干了什么吧。
二、innobase_start_or_create_for_mysql
1.
当buffer pool size大于等于1000MB时,在innodb层最大允许等待同一信号量的线程数srv_max_n_threads=5000
当1000MB >= buffer pool size >= 8MB,只使用1个bp实例,srv_max_n_threads = 10000
当buffer pool size小于8MB时,只使用1个bp实例,srv_max_n_threads = 1000
2.调用srv_boot
srv_normalize_init_values(void) //初始化全局变量
srv_general_init(void) //Initialize synchronization primitives, memory management, and thread local storag
srv_init();
初始化各种全局信号量,kernel_mutex、srv_sys以及srv_sys->threads数组中每个slot的event(调用os_event_create初始化),同样的还需要初始化srv_mysql_table数组(类型为srv_slot_t)
dict_ind_init();//初始化dict_ind_redundant和dict_ind_compact,为infimum 和supremum 记录 创建一个名为SYS_DUMMY1/SYS_DUMMY2的表结构,暂时不了解用途。
初始化srv_conc_slots
3.创建临时文件srv_dict_tmpfile和srv_misc_tmpfile,调用函数os_file_create_tmpfile()来创建
看起来会把一些诸如监控信息写入到这两个临时文件中,暂不清楚用途。
4.检查IO线程数,不应超过SRV_MAX_N_IO_THREADS(130个)
io_limit = 8 * SRV_N_PENDING_IOS_PER_THREAD; //256,表示每个segment(对应一个io线程)最大pending aio
然后做初始化:
os_aio_init(io_limit,
srv_n_read_io_threads,
srv_n_write_io_threads,
SRV_MAX_N_PENDING_SYNC_IOS);
异步IO线程包括insert buffer thread、log thread及读线程和写线程,另外还有一个os_aio_sync_array(对应AIO类型为OS_AIO_SYNC)
5.初始化fil_system(fil_init)
6.初始化buffer pool
函数err = buf_pool_init(srv_buf_pool_size, srv_buf_pool_instances);
–>分配每个buffer pool实例的内存
–>buf_LRU_old_ratio_update(100 * 3/ 8, FALSE) //更新每个bp实例的buf_pool->LRU_old_ratio
–>btr_search_sys_create//初始化adaptive hash index
为btr_search_sys、btr_search_latch_part、btr_search_sys->hash_index分配内存
adaptive hash index占的内存应为buffer pool的1/64,然后在平分到每个ahi实例上(5.5.28之前有bug,是每个ahi实例上为1/64的bp大小,当使用多实例ahi时,可能造成内存消耗过大)
7.
fsp_init() //空函数
log_init() //初始化log_sys
lock_sys_create(srv_lock_table_size); //初始化lock_sys,其中srv_lock_table_size = 5 * (srv_buf_pool_size / UNIV_PAGE_SIZE)
8.创建IO线程,回调函数为io_handler_thread
9.创建或打开ibdata数据文件open_or_create_data_files
–>调用fil_read_flushed_lsn_and_arch_log_no去读取ibdata的第一个page中记录的flushed_lsn,必须保证其他数据文件小于flush_lsn的脏页都被刷到磁盘
–>关闭文件
–>fil_space_create //初始化ibdata对应tablespace的fil_space_t结构,并插入到fil_system->spaces、fil_system->name_hash和fil_system->space_list中。
–>fil_node_create //初始化ibdata的fil_node_t并加到space->chain中
如果开启了独立的double write文件,则还需要创建或初始化double write文件(通过innodb_doublewrite_file来控制)
10.依次打开或创建iblog文件,调用函数open_or_create_log_file,同样需要将其加入到fil_system中
11.fil_open_log_and_system_tablespace_files
依次打开space->chain上的每个node对应的文件,也就是前面提到的iblog和ibdata文件,这些文件被一直打开,直到实例被shutdown。
12.检查系统table space的类型是否被支持trx_sys_file_format_max_check
13.buf_pool_invalidate(); //将buffer pool中所有page清空,所有bp中的page必须是可替换的,无脏页且无latch.
14.开始进行崩溃恢复操作,应用redo log
err = recv_recovery_from_checkpoint_start(LOG_CHECKPOINT,
IB_ULONGLONG_MAX,
min_flushed_lsn,
max_flushed_lsn);
a.创建recovery子系统 recv_sys //recv_sys_create
b.recv_sys_init(buf_pool_get_curr_size())
buf_flush_init_flush_rbt();//初始化红黑树,主要用来加速插入到flush_list上。对应rbt在每个buf_pool->flush_rbt上
确定recv_n_pool_free_frames的大小,该值表示当我们扫描日志并存储扫描到的记录到bp中时,必须至少保留这么多空闲的frame.这样我们就可以把数据page读入内存并执行日志记录
当bp>=10MB时, recv_n_pool_free_frames = 512;
当bp>=32MB时,recv_n_pool_free_frames = 1024;
为recv_sys->buf分配2MB内存,及其他相关结构成员内存分配
c.设置recv_recovery_on = TRUE;表明recovery已经开始了,在很多代码逻辑里,都需要根据这个变量来判断是否处于崩溃恢复状态。
d.从日志组里找到最大的checkpoint
err = recv_find_max_checkpoint(&max_cp_group, &max_cp_field);
背景:一个日志文件头的偏移量相关含义
LOG_GROUP_ID | 0 | 日志组ID |
LOG_FILE_START_LSN | 4 | 在该日志文件中数据开始的LSN |
LOG_FILE_NO | 12 | 4 byte的归档日志文件号,暂不清楚归档日志用途 |
LOG_FILE_WAS_CREATED_BY_HOT_BACKUP | 16 |
当日志文件是通过ibbackup –restore产生时,保留32-byte记录了 字符串‘ibbackup’以及日志的创建时间 |
LOG_FILE_OS_FILE_LOG_BLOCK_SIZE | 64 | 用于记录xtradb的log_block_size,在xtradb中这是可调整的,默认为512 |
LOG_FILE_ARCH_COMPLETED | OS_FILE_LOG_BLOCK_SIZE | 4-byte,为true表示归档日志完成 |
LOG_FILE_END_LSN | OS_FILE_LOG_BLOCK_SIZE + 4 | lsn where the archived log file at least extends: actually the archived log file may extend to a later lsn, as long as it is within the same log block as this lsn; this field is defined only when an archived log file has been completely written |
LOG_CHECKPOINT_1 | OS_FILE_LOG_BLOCK_SIZE | 日志文件中的第一个checkpoint字段,当完成一次新的checkpoint 时,可选的选择是否记录,注意这只记录在第一个日志文件头部 |
LOG_CHECKPOINT_2 | (3 * OS_FILE_LOG_BLOCK_SIZE) | 日志头部的第二个checkpoint字段 |
LOG_FILE_HDR_SIZE | 4 * OS_FILE_LOG_BLOCK_SIZE | 日志头部长度,为4个log block size |
在一个while循环中
>>读取每个log头部的checkpoint 字段(log_group_read_checkpoint_info(group, field))
>>验证checksum(函数recv_check_cp_is_consistent,读取头部的LOG_CHECKPOINT_CHECKSUM_1及LOG_CHECKPOINT_CHECKSUM_2)
>>读取checkpoint字段记录的LOG_CHECKPOINT_LSN、LOG_CHECKPOINT_NO、LOG_CHECKPOINT_OFFSET
>>比较checkpoint no,找出最大的那个日志文件
e.从找到的最大checkpoint_lsn开始扫描,需要做一个预处理:
contiguous_lsn = ut_uint64_align_down(recv_sys->scanned_lsn,
OS_FILE_LOG_BLOCK_SIZE);
对contiguous_lsn 以log block size做对齐处理
g.开始遍历日志文件,从contiguous_lsn 开始读取日志记录,函数recv_group_scan_log_recs
>>调用函数log_group_read_log_seg读取一个日志段到内存中,每个段默认4个page(RECV_SCAN_SIZE)
>>扫描日志数据记录 //recv_scan_log_recs
>>>遍历刚刚读取到内存中的日志数据的每一个block
>>>检查block头部的block no以及checksum信息
>>>如果这个block是一次flush操作的开始(log_block_get_flush_bit(log_block)),则意味着该block之前的刷新操作都已经完成了,因此更新contiguous_lsn为scanned_lsn(不太理解)
>>>找到block中第一条mtr日志的起点位置
>>>如果当前scanned_lsn > recv_sys->scanned_lsn,表明有新的entries,随后需要解析这些记录,做崩溃恢复
recv_init_crash_recovery–>fil_load_single_table_tablespaces()
—遍历data目录,读取其中所有的ibd文件
—从每个ibd的第一个page获取space id//fsp_header_get_space_id
—打开文件,创建文件节点加入到fil_system中//fil_load_single_table_tablespace
–>srv_force_recovery小于6(SRV_FORCE_NO_LOG_REDO)时,需要从double write buffer中检查page,如果数据文件中的page是损坏的,则从double write buffer中恢复(调用函数trx_sys_doublewrite_init_or_restore_pages)>>>将日志记录拷贝到recv_sys->buf中//recv_sys_add_to_parsing_buf
>>>recv_parse_log_recs–解析日志记录(recv_parse_log_rec),获取其mtr 类型,space id,page no以及指向日志记录的指针,并将其存储到hash中(recv_add_to_hash_table),用于后续的merge.这里会针对一个mtr有一条还是多条日志记录分别作处理。
>>>如果存储在hash table中的记录超出限制,则调用recv_apply_hashed_log_recs来应用这些日志 //稍后细述如何应用
h.recv_synchronize_groups(up_to_date_group);//将最新日志组中的信息()
i.其他…
15.dict_boot();//向dict_sys中加载系统表
16.trx_sys_init_at_db_start() //获取undo
a.读取ibdata中的第FSP_TRX_SYS_PAGE_NO个Page,从该page的第TRX_SYS偏移量开始表示
sys_header = trx_sysf_get(&mtr);
b.trx_rseg_list_and_array_init->trx_rseg_create_instance
遍历128(TRX_SYS_N_RSEGS)个回滚段,读取sys_header存储的每个回滚段slot的page no
如果该回滚段slot没有被使用,即page_no = FIL_NULL,则设置sys->rseg_array[n] = NULL
否则从sys_header头读取该回滚段slot所对应的space id(trx_sysf_rseg_get_space),并在内存中创建回滚段对象(trx_rseg_mem_create),读取所有undo page的信息
>>初始化当前回滚段的trx_rseg_t信息,分配内存,并将其加入到trx_sys->rseg_list中
>>读取回滚段头部数据到内存rseg_header = trx_rsegf_get_new(space, zip_size, page_no, mtr);
>>sum_of_undo_sizes = trx_undo_lists_init(rseg);
根据rseg_header初始化undo log list,并将读取的undo log加入到 rseg->insert_undo_list和rseg->insert_undo_cached中
具体没有深入进去看。
>>获取回滚段trx_rseg_t的last_trx_no、last_del_marks、last_page_no、last_offset,并封装成rseg_queue_t存储到binary heap中,这样便于根据事务id进行排序
c.基于undo log list,构建需要回滚的事务,并加入到trx_sys->trx_list中(trx_list_insert_ordered)
对于处于prepare状态的事务,需要用户在重启后手动commit或rollback掉
d.最后创建purge_sys子系统 //trx_purge_sys_create
17.fsp_header_get_free_limit();//在5.6已经移除了,貌似没啥用,会做一次checkpoint
18.recv_recovery_from_checkpoint_finish()
执行完剩下的redolog(recv_apply_hashed_log_recs,之前是hash 满了才执行)
如果打开了innodb_recovery_stats选项,还会打印一些recovery统计信息、binlog和relay log 信息等
然后设置recv_recovery_on = FALSE;
最后调用trx_rollback_or_clean_recovered(FALSE); //FALSE表示只回滚ddl操作,DML随后通过独立线程来完成
19. dict_check_tablespaces_and_store_max_id
检查每个表的tablespace id,如果之前做过crash recovery,需要检查是否和数据词典的一致(fil_space_for_table_exists_in_mem)
如果是正常shutdown,则正常打开表(fil_open_single_table_tablespace)
最后设置fil_system->max_assigned_id
20.recv_recovery_rollback_active();
a.row_merge_drop_temp_indexes(); //删除未创建完成的索引
b.row_mysql_drop_temp_tables(); //删除临时表
c.创建一个单独的线程来对从undo构建的事务做回滚或清理,回调函数为trx_rollback_or_clean_all_recovered
至此recovery的流程算是完成了……剩下的就是创建后台线程等工作