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;
}

  

 

posted @ 2022-04-13 11:59  卷毛狒狒  阅读(707)  评论(0编辑  收藏  举报