需要对接的接口以及每个接口的实现
recovery 阶段
此阶段由 xa.cc 文件中的 xarecover_handlerton()
函数完成,它通过三个接口实现与存储引擎的沟通:recover()
,commit_by_xid()
,rollback_by_xid()
。其流程如下:
- 此函数首先已经收集到了最后一个 binlog 文件中成功提交了的事物 xid,将其放入一个 SET 中(具体是不是从最后一个 binlog 文件收集的,怎么收集的,下层存储引擎不需要知道)。
- 调用
recover()
收集存储引擎中处于 prepared 阶段的事务,对于其中的每一个事物的 xid 判断其是否存在于上述 SET 。 - 如果 xid 存在于 SET 中,则调用
commit_by_xid()
。否则则调用rollback_by_xid()
。
/** This function is used to recover X/Open XA distributed transactions.
@return number of prepared transactions stored in xid_list */
static int innobase_xa_recover(
handlerton *hton, /*!< in: InnoDB handlerton */
XA_recover_txn *txn_list, /*!< in/out: prepared transactions */
uint len, /*!< in: number of slots in xid_list */
MEM_ROOT *mem_root); /*!< in: memory for table names */
// init
innobase_hton->recover = innobase_xa_recover;
返回值
表示收集到的事务的数量
*txn_list
用于返回事务链。对于每一个处于prepare 状态的事务,InnoDB 做如下工作:
- 将其事务 xid 写入 txn_list
- 将事务对应的 table 信息 push 入 txn_list
// xid
txn_list->id = *trx->xid;
txn_list->mod_tables = new (mem_root) List<st_handler_tablename>();
// tables
for (auto dd_table : trx->mod_tables) {
st_handler_tablename *table = new (mem_root) st_handler_tablename();
if (!table || get_table_name_info(table, dd_table, mem_root) ||
txn_list->mod_tables->push_back(table, mem_root))
}
len
表示 txn_list 最长的长度,InnoDB 中如果 prepared 阶段的事务数量达到了 len 个,直接返回。
if (count == len)
break;
/** This function is used to commit one X/Open XA distributed transaction which is in the prepared state
@return 0 or error number */
static xa_status_code innobase_commit_by_xid(
handlerton *hton, /*!< in: InnoDB handlerton */
XID *xid); /*!< in: X/Open XA transaction identification */
innobase_hton->commit_by_xid = innobase_commit_by_xid;
这个接口由于底层实现原因,可以不用参考 InnoDB 具体的操作,只需要在引擎中完成以下操作即可:
- 对该 xid 对应的事务完成恢复。
- 补充该事务的 commit_record。
/** This function is used to rollback one X/Open XA distributed transaction
which is in the prepared state
@return 0 or error number */
static xa_status_code innobase_rollback_by_xid(
handlerton *hton, /*!< in: InnoDB handlerton */
XID *xid); /*!< in: X/Open XA transaction identification */
innobase_hton->rollback_by_xid = innobase_rollback_by_xid;
直接抛弃 xid 对应的事务,不做恢复。
甚至可以不用实现该接口,在恢复完成后抛弃没有恢复的事务就行。
XID 的设计有点坑:
用于恢复的 xid 本身只是一个 uint64 类型,但 mysql 设计的 xid_t 类型占用了 152 byte 。真正的 xid 被存进了 data 字符数组中。
按道理日志只需要记录 uint64 大小的 xid 就行,但是 xit_t 并没有提供设置其 xid 的办法,只有 set_data() 设置其 data 数据。
目前在 prepare_record 中将整个 xid_t 的内容都存入了(152b),恢复时会恢复一个完整的 xid_t。
#define XIDDATASIZE 128
typedef struct xid_t {
private:
/**
-1 means that the XID is null
*/
long formatID;
/**
value from 1 through 64
*/
long gtrid_length;
/**
value from 1 through 64
*/
long bqual_length;
/**
distributed trx identifier. not \0-terminated.
*/
char data[XIDDATASIZE];
...
} XID;
prepare 阶段
此阶段只有一个 stage,对应 prepare 接口
/** This function is used to prepare an X/Open XA distributed transaction.
@return 0 or error number */
static int innobase_xa_prepare(handlerton *hton, /*!< in: InnoDB handlerton */
THD *thd, /*!< in: handle to the MySQL thread of
the user whose XA transaction should
be prepared */
bool all); /*!< in: true - prepare transaction
false - the current SQL statement
ended */
// init
innobase_hton->prepare = innobase_xa_prepare;
此接口需要实现两个功能:
- 判断事务是否需要回滚,如果需要,则返回错误码。
- 如果事务可以正常提交,则生成该事务的日志,生成 prepare_record(其中存储 xid),并等待落盘完成。
commit
分为了三个阶段,三个阶段之间可以并发进行,但每个阶段内顺序运行:第一个进入某个阶段的事务为 leader,后续进入的事务为 follower。leader 所在的线程领导 follower 顺序完成此阶段的任务。
- 其中第一个阶段刷 redo-log,进入这个阶段的所有事务只刷盘一次。除了刷盘 redo-log,bin-log 需要在这个阶段顺序产生,这样一来保证了 redo-log 和 bin-log 日志中事务的顺序是相同的。
- 第二个阶段刷 bin-log
- 第三个阶段执行 commit,顺序调用存储引擎的 commit 接口
/** Flush InnoDB redo logs to the file system.
@param[in] hton InnoDB handlerton
@param[in] binlog_group_flush true if we got invoked by binlog
group commit during flush stage, false in other cases.
@return false */
static bool innobase_flush_logs(handlerton *hton, bool binlog_group_flush);
innobase_hton->flush_logs = innobase_flush_logs;
这个接口的作用是等待 redo_log 进行刷盘,是进行组提交的接口。但是测试发现,每一个事务prepare 之后都会调用此接口,且 InnoDB 都会执行刷盘或者等待刷盘操作。
int ha_commit_low(THD *thd, bool all, bool run_after_commit = true);
innobase_hton->commit = innobase_commit;
此接口需要实现两个功能:
- 这个接口不需要做回滚操作,如果事务没有执行成功,需要执行回滚,只需要返回一个错误码。mysql 随后会调用
rollback
接口回滚。 - 执行存储引擎的提交操作,并在提交结束后,生成一条 commit_record 日志记录,这里不需要等待此日志记录落盘,因为此日志记录是否落盘不影响已经提交了的事务对应的 tuple 的可见性。且日志的完整性已由 prepare_record 保证。