分散式事務RT模式可行性報告(待完善)

windawake發表於2022-02-14

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+樹的資料結構。

參考文獻

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

本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章