分布式事务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 协议》,转载必须注明作者和本文链接
:smiley: 向大佬学习
牛顿被苹果砸中,博主被狗子遛弯。。。
别人家的孩子,还有别人家的狗 :sob: