当 MySQL关闭后,重启MySQL时,会进行 crash recovery操作,这里分析一下MySQL是如何进行的:
1. 首先在启动Innodb存储引擎时会回滚事务系统的事务列表中未在Innodb中提交的处于 TRX_STATE_ACTIVE 状态的不完整的事务【在事务的两阶段提交过程中,xa prepare阶段会在Innodb中将事务的状态修改为 TRX_STATE_PREPARED状态】。核心代码如下:
void trx_rollback_or_clean_recovered( /*============================*/ ibool all) /*!< in: FALSE=roll back dictionary transactions; TRUE=roll back all non-PREPARED transactions */ { trx_t* trx; ut_a(srv_force_recovery < SRV_FORCE_NO_TRX_UNDO); if (trx_sys_get_n_rw_trx() == 0) { return; } if (all) { ib::info() << "Starting in background the rollback" " of uncommitted transactions"; } /* Note: For XA recovered transactions, we rely on MySQL to do rollback. They will be in TRX_STATE_PREPARED state. If the server is shutdown and they are still lingering in trx_sys_t::trx_list then the shutdown will hang. */ /* Loop over the transaction list as long as there are recovered transactions to clean up or recover. */ do { trx_sys_mutex_enter(); for (trx = UT_LIST_GET_FIRST(trx_sys->rw_trx_list); trx != NULL; trx = UT_LIST_GET_NEXT(trx_list, trx)) { assert_trx_in_rw_list(trx); /* If this function does a cleanup or rollback then it will release the trx_sys->mutex, therefore we need to reacquire it before retrying the loop. */ if (trx_rollback_resurrected(trx, all)) { trx_sys_mutex_enter(); break; } } trx_sys_mutex_exit(); } while (trx != NULL); if (all) { ib::info() << "Rollback of non-prepared transactions" " completed"; } } static ibool trx_rollback_resurrected( /*=====================*/ trx_t* trx, /*!< in: transaction to rollback or clean */ ibool all) /*!< in: FALSE=roll back dictionary transactions; TRUE=roll back all non-PREPARED transactions */ { ut_ad(trx_sys_mutex_own()); /* The trx->is_recovered flag and trx->state are set atomically under the protection of the trx->mutex (and lock_sys->mutex) in lock_trx_release_locks(). We do not want to accidentally clean up a non-recovered transaction here. */ trx_mutex_enter(trx); bool is_recovered = trx->is_recovered; trx_state_t state = trx->state; trx_mutex_exit(trx); if (!is_recovered) { return(FALSE); } switch (state) { case TRX_STATE_COMMITTED_IN_MEMORY: trx_sys_mutex_exit(); ib::info() << "Cleaning up trx with id " << trx_get_id_for_print(trx); trx_cleanup_at_db_startup(trx); trx_free_resurrected(trx); return(TRUE); case TRX_STATE_ACTIVE: if (all || trx_get_dict_operation(trx) != TRX_DICT_OP_NONE) { trx_sys_mutex_exit(); trx_rollback_active(trx); trx_free_for_background(trx); return(TRUE); } return(FALSE); case TRX_STATE_PREPARED: return(FALSE); case TRX_STATE_NOT_STARTED: case TRX_STATE_FORCED_ROLLBACK: break; } ut_error; return(FALSE); }
2. 在 Innodb存储引擎启动之后根据 binlog 进行 xa recovery;Innodb解析 redo log,读取出所有处于 prepare 状态的事务的 xid,而后在读取最后一个 binlog 的 xid 的hash表中查找 xid 是否存在,存在则 commit,不存在则 rollback。
static my_bool xarecover_handlerton(THD *unused, plugin_ref plugin, void *arg) { // 目前仅关注 Innodb handlerton *hton= plugin_data<handlerton*>(plugin); struct xarecover_st *info= (struct xarecover_st *) arg; int got; if (hton->state == SHOW_OPTION_YES && hton->recover) { // 调用 Innodb 存储引擎的 recover 函数, Innodb会解析 redo log,读出所有处于 prepare 状态的事务,返回事务的 xid while ((got= hton->recover(hton, info->list, info->len)) > 0) { sql_print_information("Found %d prepared transaction(s) in %s", got, ha_resolve_storage_engine_name(hton)); // 遍历 Innodb 返回的 xid for (int i= 0; i < got; i++) { my_xid x= info->list[i].get_my_xid(); if (!x) // not "mine" - that is generated by external TM { #ifndef NDEBUG char buf[XIDDATASIZE * 4 + 6]; // see xid_to_str XID *xid= info->list + i; sql_print_information("ignore xid %s", xid->xid_to_str(buf)); #endif transaction_cache_insert_recovery(info->list + i); info->found_foreign_xids++; continue; } if (info->dry_run) { info->found_my_xids++; continue; } // recovery mode // 在最后一个 binlog 中读取的xid的 hash 表中查找 xid,如果找到了,则说明事务记录了binlog, // 在 Innodb 中 进行提交。如果找不到,则进行回滚。 if (info->commit_list ? my_hash_search(info->commit_list, (uchar *)&x, sizeof(x)) != 0 : tc_heuristic_recover == TC_HEURISTIC_RECOVER_COMMIT) { #ifndef NDEBUG char buf[XIDDATASIZE * 4 + 6]; // see xid_to_str XID *xid= info->list + i; sql_print_information("commit xid %s", xid->xid_to_str(buf)); #endif hton->commit_by_xid(hton, info->list + i); } else { #ifndef NDEBUG char buf[XIDDATASIZE * 4 + 6]; // see xid_to_str XID *xid= info->list + i; sql_print_information("rollback xid %s", xid->xid_to_str(buf)); #endif hton->rollback_by_xid(hton, info->list + i); } } if (got < info->len) break; } } return false; }