MySQL · 特性分析 · MySQL 5.7 外部XA Replication实现及缺陷分析
MySQL 5.7 外部XA Replication实现及缺陷分析
MySQL 5.7增强了分布式事务的支持,解决了之前客户端退出或者服务器关闭后prepared的事务回滚和服务器宕机后binlog丢失的情况。
为了解决之前的问题,MySQL5.7将外部XA在binlog中的记录分成了两部分,使用两个GTID来记录。执行prepare的时候就记录一次binlog,执行commit/rollback再记录一次。由于XA是分成两部分记录,那么XA事务在binlog中就可能是交叉出现的。Slave端的SQL线程在apply的时候需要能够在这些不同事务间切换。
但MySQL XA Replication的实现只考虑了Innodb一种事务引擎的情况,当添加其他事务引擎的时候,原本的一些代码逻辑就会有问题。同时MySQL源码中也存在宕机导致主备不一致的缺陷。
MySQL 5.7 外部XA Replication源码剖析
Master写入
当执行 XA START ‘xid’后,内部xa_state进入XA_ACTIVE状态。
bool Sql_cmd_xa_start::trans_xa_start(THD *thd)
{
xid_state->set_state(XID_STATE::XA_ACTIVE);
第一次记录DML操作的时候,通过下面代码可以看到,对普通事务在binlog的cache中第一个event记录’BEGIN’,如果是xa_state处于XA_ACTIVE状态就记录’XA START xid’,xid为序列化后的。
static int binlog_start_trans_and_stmt(THD *thd, Log_event *start_event)
{
if (cache_data->is_binlog_empty())
{
if (is_transactional && xs->has_state(XID_STATE::XA_ACTIVE))
{
/*
XA-prepare logging case.
*/
qlen= sprintf(xa_start, "XA START %s", xs->get_xid()->serialize(buf));
query= xa_start;
}
else
{
/*
Regular transaction case.
*/
query= begin;
}
Query_log_event qinfo(thd, query, qlen,
is_transactional, false, true, 0, true);
if (cache_data->write_event(thd, &qinfo))
DBUG_RETURN(1);
XA END xid的执行会将xa_state设置为XA_IDLE。
bool Sql_cmd_xa_end::trans_xa_end(THD *thd)
{
xid_state->set_state(XID_STATE::XA_IDLE);
当XA PREPARE xid执行的时候,binlog_prepare会通过检查thd的xa_state是否处于XA_IDLE状态来决定是否记录binlog。如果在对应状态,就会调用MYSQL_BINLOG的commit函数,记录’XA PREPARE xid’,将之前cache的binlog写入到文件。
static int binlog_prepare(handlerton *hton, THD *thd, bool all)
{
DBUG_RETURN(all && is_loggable_xa_prepare(thd) ?
mysql_bin_log.commit(thd, true) : 0);
inline bool is_loggable_xa_prepare(THD *thd)
{
return DBUG_EVALUATE_IF("simulate_commit_failure",
false,
thd->get_transaction()->xid_state()->
has_state(XID_STATE::XA_IDLE));
TC_LOG::enum_result MYSQL_BIN_LOG::commit(THD *thd, bool all)
{
if (is_loggable_xa_prepare(thd))
{
XID_STATE *xs= thd->get_transaction()->xid_state();
XA_prepare_log_event end_evt(thd, xs->get_xid(), one_phase);
err= cache_mngr->trx_cache.finalize(thd, &end_evt, xs)
}
当XA COMMIT/ROLLBACK xid执行时候,调用do_binlog_xa_commit_rollback记录’XA COMMIT/ROLLBACK xid’。
TC_LOG::enum_result MYSQL_BIN_LOG::commit(THD *thd, bool all)
{
if (thd->lex->sql_command == SQLCOM_XA_COMMIT)
do_binlog_xa_commit_rollback(thd, xs->get_xid(),
true)))
int MYSQL_BIN_LOG::rollback(THD *thd, bool all)
{
if (thd->lex->sql_command == SQLCOM_XA_ROLLBACK)
if ((error= do_binlog_xa_commit_rollback(thd, xs->get_xid(), false)))
由于XA PREPARE单独记录binlog,那么binlog中的events一个xa事务就可能是分隔开的。举个例子,session1中xid为’a’的分布式事务执行xa prepare后,session2中执行并提交了xid为’z’的事务,然后xid ‘a’才提交。我们可以看到binlog events中xid ‘z’的events在’a’的prepare和commit之间。
session1:
xa start 'a';
insert into t values(1);
xa end 'a';
xa prepare 'a';
session2:
xa start 'z';
insert into t values(2);