MySQL启动过程详解四:crash recovery
当 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解析 binlog,读取出所有已经完全写入 binlog的 xid,而后判断处于 rw_trx_list 中的事务的 xid 在 最后一个binlog的 xid 的 hash 表中是否存在,存在则 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;
}
浙公网安备 33010602011771号