分布式事务RT模式可行性报告(待完善)

RT模式是分布式事务理论上的一次创新,能够完美地解决业务在特别复杂场景下的数据一致性问题,性能比tcc稍差,但是数据一致性、代码易用性和低资源开销远超tcc和AT。采用重置机制从而改变以往采用补偿机制去实现分布式事务,所以很难被程序员们接受,故此写了一篇可行性报告,通过mysql源码进行理论上的证明。想了解RT模式,请阅读:博客:Laravel基于RT模式实现分布式事务(全球首创支持子服务嵌套事务)

使用RT模式,程序的执行过程中可能会产生下列的sql语句

begin;
insert into reset_order(id, order_no)values(1,  "demo01");
rollback;

insert into reset_order(id, order_no)values(2, "demo02");

xa start "rt001";
insert into reset_order(id, order_no)values(1,  "demo01");
xa end "rt001";
xa prepare "rt001";
xa commit "rt001";

读者可能会觉得奇怪,为何rollback最后还xa重新执行一次?这是因为使用重置机制,并且是带上记忆的重置,《re:从零开始的异世界生活》动漫里,男主正因为不断地重置,才能最终完成世界上不可能完成的任务。

调试环境:mysql-8.0.21源码 + gdb + vscode

疑问1:使用mysql xa会不会导致mysql表性能变差?

/**
  xa start "rt001";
  @see sql/xa.cc:1070
*/
bool Sql_cmd_xa_start::trans_xa_start(THD *thd) {
    ...
    xid_state->set_state(XID_STATE::XA_ACTIVE);
    ...
    else if (!trans_begin(thd)) {
    ...
}

/**
  xa end "rt001";
  @see sql/xa.cc:1128
*/
bool  Sql_cmd_xa_end::trans_xa_end(THD  *thd) {
    ...
    xid_state->set_state(XID_STATE::XA_IDLE);
    ...
}

xa start "rt001"把xa状态更新为XA_ACTIVE然后走普通事务trans_begin的代码。xa end "rt001"只是把xa状态更新为XA_IDLE。这两步不会影响性能。

/**
  xa prepare "rt001";
  @see sql/binlog.cc:8267
*/
THD *MYSQL_BIN_LOG::fetch_and_process_flush_stage_queue(const bool check_and_skip_flush_logs) {
    ...
    /*
      We flush prepared records of transactions to the log of storage engine (for example, InnoDB redo log) in a group right before flushing them to binary log.
    */
    ha_flush_logs(true);
    ...
}

/**
  xa commit "rt001";
  @see storage/innobase/lock/lock0lock.cc:4149
*/
static void lock_release(trx_t *trx) {
    ...
    lock_table_dequeue(lock);
    ...
}

普通事务的commit或者不开启事务的dml语句,都有tc_log->prepare()tc_log->commit()两个阶段,相当于xa prepare和xa commit两个阶段。按理来讲,xa这两步也不会影响性能。但是问题在于xa prepare把事务内sql语句写到binlog,使reset_order表有行级锁,等到xa commit才把锁释放掉,如果prepare阶段之后网络中断,那么锁就一直留在表里,锁越多表的性能就越差。RT模式需要做xa prepare事务的检查并且提交或者回滚,就能保证数据库表性能不会变差。

疑问2:mysql回滚后再插入相同的记录,会不会破坏B+树的数据结构

/**
  xa事务内 insert into reset_order(id, order_no)values(1,  "demo01");
  @see sql/handler.cc:3683
*/
int handler::update_auto_increment() {
    ...
    if ((nr = table->next_number_field->val_int()) != 0 || (table->autoinc_field_has_explicit_non_null_value && thd->variables.sql_mode & MODE_NO_AUTO_VALUE_ON_ZERO)) {
    ...
        insert_id_for_cur_row = 0;  // didn't generate anything
        return 0;
    }
    ...
}

/** 
  xa事务内 insert into reset_order(id, order_no)values(1, "demo01"); 
  @see storage/innobase/handler/ha_innodb.cc:8483 
*/
int  ha_innobase::write_row(uchar  *record)
{
    ...
    if ((error_result = update_auto_increment()))
    {
    ...
    }
    ...
    auto_inc = innobase_next_autoinc(auto_inc, 1, increment, offset, col_max_value);
    ...
}

由于insert_id_for_cur_row = 0,所以插入记录前,主键id不需要调整,插入记录后,更新主键id。暂时没看出来会破坏B+树的数据结构。

个人笔记

2021年,我最初想仿造seata AT模式开发一个组件,但是发现实现起来太难,补偿机制带来的bug太多了。自发反思起来,难道世界上就没有数据一致性100%,零bug,简单易用,高性能的分布式事务吗。周六日的时候,我在小公园遛狗一直被狗狗带着绕水池转,转了很多圈,我突然茅塞顿开,除了补偿机制,还可以用重置机制。果然用了重置机制,发现能消灭补偿机制带了的90%各种问题。

参考文献

zhuanlan.zhihu.com/p/372300181
www.programminghunter.com/article/...
www.cnblogs.com/zzyhogwarts/p/1496...

本作品采用《CC 协议》,转载必须注明作者和本文链接
windawake
《L05 电商实战》
从零开发一个电商项目,功能包括电商后台、商品 & SKU 管理、购物车、订单管理、支付宝支付、微信支付、订单退款流程、优惠券等
《L04 微信小程序从零到发布》
从小程序个人账户申请开始,带你一步步进行开发一个微信小程序,直到提交微信控制台上线发布。
讨论数量: 4

牛顿被苹果砸中,博主被狗子遛弯。。。

3年前 评论
windawake (楼主) 2年前
黑将军

别人家的孩子,还有别人家的狗 :sob:

2年前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!