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 協議》,轉載必須註明作者和本文連結