1. 程式人生 > >分散式事務之——MySQL對XA事務的支援

分散式事務之——MySQL對XA事務的支援

 MySQL 從5.0.3開始支援XA分散式事務,且只有InnoDB儲存引擎支援。MySQL Connector/J 從5.0.0版本之後開始直接提供對XA的支援。


 需要注意的是, 在DTP模型中,mysql屬於資源管理器(RM)。而一個完整的分散式事務中,一般會存在多個RM,由事務管理器TM來統一進行協調。因此,這裡所說的mysql對XA分散式事務的支援,一般指的是單臺mysql例項如何執行自己的事務分支。

MySQL XA 事務SQL語法

XA {START|BEGIN} xid [JOIN|RESUME]   //開啟XA事務,如果使用的是XA START而不是XA BEGIN,那麼不支援[JOIN|RESUME],xid是一個唯一值,表示事務分支識別符號
XA END xid [SUSPEND [FOR MIGRATE]]   //結束一個XA事務,不支援[SUSPEND [FOR MIGRATE]]
XA PREPARE xid 準備提交
XA COMMIT xid [ONE PHASE] //提交,如果使用了ONE PHASE,則表示使用一階段提交。兩階段提交協議中,如果只有一個RM參與,那麼可以優化為一階段提交
XA ROLLBACK xid  //回滾
XA RECOVER [CONVERT XID]  //列出所有處於PREPARE階段的XA事務
下面是一個簡單的msyql XA事務案例,演示了mysql作為全域性事務中的一個事務分支,將一行記錄插入到一個表中
mysql> XA START 'xatest’;  //其中'xatest’就是xid的值
Query OK, 0 rows affected (0.00 sec)
 
mysql> insert into user(name) values("tianshozuhi");
Query OK, 1 row affected (0.00 sec)
 
mysql> XA END 'xatest';
Query OK, 0 rows affected (0.00 sec)
 
mysql> XA PREPARE 'xatest';
Query OK, 0 rows affected (0.01 sec)
 
mysql> XA COMMIT 'xatest';
Query OK, 0 rows affected (0.01 sec)

Mysql XA事務狀態

XA事務的狀態,按照如下步驟進行展開

1.    使用XA START來啟動一個XA事務,並把它置於ACTIVE狀態。

2.    對於一個ACTIVE狀態的 XA事務,我們可以執行構成事務的SQL語句,然後釋出一個XA END語句。XA END把事務放入IDLE狀態。

3.    對於一個IDLE 狀態XA事務,可以執行一個XA PREPARE語句或一個XA COMMIT…ONE PHASE語句:

  • XA PREPARE把事務放入PREPARED狀態。在此點上的XA RECOVER語句將在其輸出中包括事務的xid值,因為XA RECOVER會列出處於PREPARED狀態的所有XA事務。

  • XA COMMIT…ONE PHASE用於預備和提交事務。xid值將不會被XA RECOVER列出,因為事務終止。

4.    對於一個PREPARED狀態的 XA事務,您可以釋出一個XA COMMIT語句來提交和終止事務,或者釋出XA ROLLBACK來回滾並終止事務。

    針對一個給定的客戶端連線而言,XA事務和非XA事務(即本地事務)是互斥的。例如,已經執行了”XA START”命令來開啟一個XA事務,則本地事務不會被啟動,直到XA事務已經被提交或被 回滾為止。相反的,如果已經使用START TRANSACTION啟動一個本地事務,則XA語句不能被使用,直到該事務被提交或被 回滾為止。

    最後,如果一個XA事務處於ACTIVE狀態,是不能直接進行提交的,如果這樣做,mysql會丟擲異常:

ERROR 1399 (XAE07): XAER_RMFAIL: The command cannot be executed
when global transaction is in the ACTIVE state

3 關於XID的說明

mysql中使用xid來作為一個事務分支的識別符號。事實上xid作為事務分支識別符號是在XA規範中定義的,在<< Distributed Transaction Processing: The XA Specification>> 4.2 節中,規定了一個xid的結構,通過C語言進行描述,如下:

    /∗
    ∗ Transaction branch identification: XID and NULLXID:
    ∗/
    #define XIDDATASIZE 128  /∗ size in bytes ∗/
    #define MAXGTRIDSIZE 64  /∗ maximum size in bytes of gtrid ∗/
    #define MAXBQUALSIZE 64  /∗ maximum size in bytes of bqual ∗/
    struct xid_t {
        long formatID;     /* format identifier */
        long gtrid_length; /* value 1-64 */
        long bqual_length; /* value 1-64 */
        char data[XIDDATASIZE];
        };
    /∗
    ∗ A value of -1 in formatID means that the XID is null.
    ∗/
    typedef struct xid_t XID;
    /∗
    ∗ Declarations of routines by which RMs call TMs:
    ∗/
    extern int ax_reg(int, XID ∗, long);
    extern int ax_unreg(int, long);

XA規範定義了一個xid有4個部分組成:

gtrid:

    全域性事務識別符號(global transaction identifier),最大不能超過64位元組

bqual:

    分支限定符(branch qualifier),最大不能超過64位元組

data:

   xid的值,其是 gtrid和bqual拼接後的內容。因為gtrid和bqual最大都是64個位元組,因此data的最大長度為128。不過,在xid的結構體中,並沒有gtrid和bqual,只有gtrid_length、bqual_length。由於二者的內容都儲存在data中,因此我們可以根據data反推出gtrid和bqual。舉例來說,假設gtrid為”g12345”(5個位元組),bqual為”b456”(4個位元組)。那麼在構造xid結構體時,gtrid_length=5,bqual_length=4,data=”g12345b456”,那麼在反推的時候:

從data[0]到data[gtrid_length-1]之間的部分就是gtrid的值;從data[gtrid_length]到data[gtrid_length+bqual_length-1]部分就是bqual的值。

formatId:

    而formatId的作用就是記錄gtrid、bqual的格式,類似於memcached中flags欄位的作用。XA規範中通過一個結構體約定了xid的組成部分,但是並沒有規定data中儲存的gtrid、bqual內容到底應該是什麼格式。你可以選擇使用數字,也可以選擇使用字串,到底選擇什麼由開發者自行決定,只要最終能保證data中的內容是全域性唯一的即可。XA規範建議使用OSI CCR風格來組織xid的內容,此時formatId應該設定為0.

在mysql官方文件中,關於xid的組成也有類似的說明:

xid: gtrid [, bqual [, formatID ]]

其中,bqual、formatID是可選的。解釋如下:

gtrid : 是一個全域性事務識別符號(global transaction identifier),

bqual:是一個分支限定符(branch qualifier),如果沒有提供bqual,那麼預設值為空字串''。

formatID:是一個數字,用於標記gtrid和bqual值的格式,這是一個無符號整數(unsigned integer),也就是說,最小為0。如果沒有提供formatID,那麼其預設值為1。

    特別需要注意的是,xid作為一個事務分支的識別符號,理論上只要有分支限定符(bqual)就可以了,為什麼要包含全域性事務識別符號(gtrid)?這主要是為了管理方便,通過包含進xid,我們可以很容易的判斷出這個事務分支屬於哪一個全域性事務。 

    例如,前面提到 XA RECOVER命令的作用是列出所有處於PREPARE階段的XA事務,以下是一個案例:

    mysql>  XA RECOVER;
    +----------+--------------+--------------+--------------+
    | formatID | gtrid_length | bqual_length | data         |
    +----------+--------------+--------------+--------------+
    |        1 |            6 |            6 | g12345b67890 |
    +----------+--------------+--------------+--------------+

這裡列出的是一個分支事務xid的組成資訊,根據前面的介紹,我們可以推斷出:

    gtrid是data[0]到data[gtrid_length-1]部分的內容,即data[0]到data[6-1=5]部分的內容,結果為g12345

    而bqual是data[gtrid_length]到data[gtrid_length+bqual_length-1]部分的內容,即data[6]到data[6+6-1=11]部分的內容,結果b67890

因此,根據這個資訊,我們就可以判斷出這個xid表示的是:全域性事務(g12345)中的事務分支(b67890)。

4、通過jdbc操作mysql xa事務

    MySQL Connector/J 從5.0.0版本之後開始直接提供對XA的支援,也就是提供了java版本XA介面的實現。意味著我們可以直接通過java程式碼來執行mysql xa事務。

    需要注意的是,業務開發人員在編寫程式碼時,不應該直接操作這些XA事務操作的介面。因為在DTP模型中,RM上的事務分支的開啟、結束、準備、提交、回滾等操作,都應該是由事務管理器TM來統一管理。

    由於目前我們還沒有接觸到TM,那麼我們不妨做一回"人肉事務管理器",用你智慧的大腦,來控制多個mysql例項上xa事務分支的執行,提交/回滾。通過直接操作這些介面,你將對xa事務有更深刻的認識。

import com.mysql.jdbc.jdbc2.optional.MysqlXAConnection;
import com.mysql.jdbc.jdbc2.optional.MysqlXid;
import javax.sql.XAConnection;
import javax.transaction.xa.XAException;
import javax.transaction.xa.XAResource;
import javax.transaction.xa.Xid;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
public class MysqlXAConnectionTest {
   public static void main(String[] args) throws SQLException {
      //true表示列印XA語句,,用於除錯
      boolean logXaCommands = true;
      // 獲得資源管理器操作介面例項 RM1
      Connection conn1 = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", "shxx12151022");
      XAConnection xaConn1 = new MysqlXAConnection((com.mysql.jdbc.Connection) conn1, logXaCommands);
      XAResource rm1 = xaConn1.getXAResource();
      // 獲得資源管理器操作介面例項 RM2
      Connection conn2 = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root",
            "shxx12151022");
      XAConnection xaConn2 = new MysqlXAConnection((com.mysql.jdbc.Connection) conn2, logXaCommands);
      XAResource rm2 = xaConn2.getXAResource();
      // AP請求TM執行一個分散式事務,TM生成全域性事務id
      byte[] gtrid = "g12345".getBytes();
      int formatId = 1;
      try {
         // ==============分別執行RM1和RM2上的事務分支====================
         // TM生成rm1上的事務分支id
         byte[] bqual1 = "b00001".getBytes();
         Xid xid1 = new MysqlXid(gtrid, bqual1, formatId);
         // 執行rm1上的事務分支
         rm1.start(xid1, XAResource.TMNOFLAGS);//One of TMNOFLAGS, TMJOIN, or TMRESUME.
         PreparedStatement ps1 = conn1.prepareStatement("INSERT into user(name) VALUES ('tianshouzhi')");
         ps1.execute();
         rm1.end(xid1, XAResource.TMSUCCESS);
         // TM生成rm2上的事務分支id
         byte[] bqual2 = "b00002".getBytes();
         Xid xid2 = new MysqlXid(gtrid, bqual2, formatId);
         // 執行rm2上的事務分支
         rm2.start(xid2, XAResource.TMNOFLAGS);
         PreparedStatement ps2 = conn2.prepareStatement("INSERT into user(name) VALUES ('wangxiaoxiao')");
         ps2.execute();
         rm2.end(xid2, XAResource.TMSUCCESS);
         // ===================兩階段提交================================
         // phase1:詢問所有的RM 準備提交事務分支
         int rm1_prepare = rm1.prepare(xid1);
         int rm2_prepare = rm2.prepare(xid2);
         // phase2:提交所有事務分支
         boolean onePhase = false; //TM判斷有2個事務分支,所以不能優化為一階段提交
         if (rm1_prepare == XAResource.XA_OK
               && rm2_prepare == XAResource.XA_OK
               ) {//所有事務分支都prepare成功,提交所有事務分支
            rm1.commit(xid1, onePhase);
            rm2.commit(xid2, onePhase);
         } else {//如果有事務分支沒有成功,則回滾
            rm1.rollback(xid1);
            rm1.rollback(xid2);
         }
      } catch (XAException e) {
         // 如果出現異常,也要進行回滾
         e.printStackTrace();
      }
   }
}

    在這個案例中,演示了2個RM的情況下分散式事務的工作流程。因為我們充當了"人肉事務管理器”TM,因此很多本應該由TM來處理的工作處理細節也直接體現在上述程式碼中,如:生成全域性事務id和分支事務id、在RM上開啟事務分支、兩階段提交等。雖然我們自己作為"人肉事務管理器”是很不可靠的,但是上述程式碼可以讓我們瞭解一個TM內部的主要工作流程是怎樣的。

    在實際開發中,程式碼絕不會像上表面那樣複雜,因為我們通常都會使用第三方或者容器提供的TM功能,因此在操作分散式事務時,程式碼可以得到極大的簡化。

    最後,由於我們設定了logXaCommands=true,程式在執行的時候回打印出執行的XA命令。如下所示:

    Fri Feb 02 18:09:29 CST 2018 DEBUG: Executing XA statement: XA START 0x673132333435,0x623030303031,0x1
    Fri Feb 02 18:09:29 CST 2018 DEBUG: Executing XA statement: XA END 0x673132333435,0x623030303031,0x1
    Fri Feb 02 18:09:29 CST 2018 DEBUG: Executing XA statement: XA START 0x673132333435,0x623030303032,0x1
    Fri Feb 02 18:09:29 CST 2018 DEBUG: Executing XA statement: XA END 0x673132333435,0x623030303032,0x1
    Fri Feb 02 18:09:29 CST 2018 DEBUG: Executing XA statement: XA PREPARE 0x673132333435,0x623030303031,0x1
    Fri Feb 02 18:09:29 CST 2018 DEBUG: Executing XA statement: XA PREPARE 0x673132333435,0x623030303032,0x1
    Fri Feb 02 18:09:29 CST 2018 DEBUG: Executing XA statement: XA COMMIT 0x673132333435,0x623030303031,0x1
    Fri Feb 02 18:09:29 CST 2018 DEBUG: Executing XA statement: XA COMMIT 0x673132333435,0x623030303032,0x1

5 MySQL Connector/J XA事務支援原始碼簡單分析

    最後,我們對上述原始碼進行一下簡單的分析。在前面直接使用mysql命令操作的時候,我們通過"XA START xid”等XA命令來執行XA事務。而在上述java程式碼中,我們是獲取了一個普通的連結Connection之後,封裝成了MysqlXAConnection。如下:

com.mysql.jdbc.jdbc2.optional.MysqlXAConnection

public class MysqlXAConnection extends MysqlPooledConnection implements XAConnection, XAResource {
  private com.mysql.jdbc.Connection underlyingConnection;
  private Log log;
  protected boolean logXaCommands;
   
  //構造方法
  public MysqlXAConnection(com.mysql.jdbc.Connection connection, boolean logXaCommands) throws SQLException {
    super(connection);
    this.underlyingConnection = connection;
    this.log = connection.getLog();
    this.logXaCommands = logXaCommands;
  }
…
}

可以看到,MysqlXAConnection本身就實現了XAResource介面,因此當呼叫getXAResource()方法時,返回的就是其自己

com.mysql.jdbc.jdbc2.optional.MysqlXAConnection#getXAResource

public XAResource getXAResource() throws SQLException {
    return this;
}

之後,我們呼叫XAResource的start方法來開啟XA事務。start方法原始碼如下所示:

com.mysql.jdbc.jdbc2.optional.MysqlXAConnection#start

public void start(Xid xid, int flags) throws XAException {
    //1、封裝XA命令
    StringBuilder commandBuf = new StringBuilder(MAX_COMMAND_LENGTH);
    commandBuf.append("XA START ");
    appendXid(commandBuf, xid);
   
    //2、新增flag標記
    switch (flags) {
        case TMJOIN:
            commandBuf.append(" JOIN");
            break;
        case TMRESUME:
            commandBuf.append(" RESUME");
            break;
        case TMNOFLAGS:
            // no-op
            break;
        default:
            throw new XAException(XAException.XAER_INVAL);
    }
    
    //執行命令
    dispatchCommand(commandBuf.toString());
    this.underlyingConnection.setInGlobalTx(true);
}
 可以看到,當我們呼叫MysqlXAConnection的start方法時,實際上就是執行了一個”XA START xid [JOIN|RESUME]”命令而已,和我們直接在命令列中的操作是一樣一樣的,只不過通過封裝簡化了我們的操作。

    對於MysqlXAConnection的end、prepare、commit、rollback等方法,也都是是類似的,不再贅述。

    最後提示, MySQL Connector/J 中提供的XA操作介面,如上面提到的XAConnection、XAResource、Xid等,實際上都遵循了JTA規範。


相關推薦

分散式事務——MySQLXA事務支援

 MySQL 從5.0.3開始支援XA分散式事務,且只有InnoDB儲存引擎支援。MySQL Connector/J 從5.0.0版本之後開始直接提供對XA的支援。 需要注意的是, 在DTP模型中,mysql屬於資源管理器(RM)。而一個完整的分散式事務中,一般會存在多個RM

分散式事務TCC與XA

TCC與XA/JTA對比 XA是資源層面的分散式事務,強一致性,在兩階段提交的整個過程中,一直會持有資源的鎖。基於資料庫鎖實現。 TCC是業務層面的分散式事務,最終一致性,不會一直持有資源的鎖。(第

mysqlGIS的支援 & oracle 空間資料庫說明

今天下午閒來無事,無意中翻看了一下mysql的手冊,以前總是很有針對性的查閱手冊的內容,重點都是放在了sql語句的一些基本細節、mysql的效能引數、儲存控制的管理等方面,但是今天無意中發現了一個mysql的重要特點,那就是mysql對gis相關的空間資料也有儲存功能,這一點

MySQL XA 事務支援調研

3. 使用限制 a. XA事務和本地事務以及鎖表操作是互斥的 開啟了xa事務就無法使用本地事務和鎖表操作 mysql> xa start 't1xa'; Query OK, 0 rows affected (0.04 sec) mysql> begin; ERROR 1399 (XAE07): X

Mysql事務支援

作者:老王 MySQL5.X都已經發布好久了,但是還有很多人認為MySQL是不支援事務處理的,這不得不怪他們是孤陋寡聞的,其實,只要你的MySQL版本支援BDB或InnoDB表型別,那麼你的MySQL就具有事務處理的能力。這裡面,又以InnoDB表型別用的最多,雖然後來發生了諸如Oracle收購InnoD

## 【分散式事務】面試官問我:MySQL中的XA事務崩潰瞭如何恢復??

## 寫在前面 > 前段時間搭建了一套MySQL分散式資料庫叢集,資料庫節點有12個,用來測試各種分散式事務方案的效能和優缺點。測試MySQL XA事務時,正當測試指令碼向資料庫中批量插入資料時,強制伺服器斷電!注意:是直接拔電源,使其瞬間斷電,再次重啟伺服器後,MySQL資料庫報錯了。特此記錄MyS

mysql數據庫從刪庫到跑路mysql:視圖、觸發器、事務、存儲過程、函數

存儲過程、函數 命名 復雜 使用 耦合 查找 根據 數據集 並且 一 視圖 視圖是一個虛擬表(非真實存在),其本質是【根據SQL語句獲取動態的數據集,並為其命名】,用戶使用時只需使用【名稱】即可獲取結果集,可以將該結果集當做表來使用。 使用視圖我們可以把查詢過程中的臨時表摘

MySQL(十三)MySQL事務

發出 最簡 -i 更改 讀取數據 兩種方法 mysql ont 之間 前言   這段時間自己會把之前學的東西都總結一遍,希望對自己以後的工作中有幫助。其實現在每天的狀態都是很累的,但是我要堅持!   進入我們今天的正題:   為什麽MySQL要 有事務呢?事務到底是用

MySQL運維系列 如何監控大事務

實用 階段 AI form 舉例 擁有 conn .com started 背景大家有沒有遇到這樣的情況 某個SQL執行特別慢,導致整個transaction一直處於running階段某個Session的SQL已經執行完了,但是遲遲沒有commit,一直處於sleep階段某

分散式事務——tcc-transaction分散式TCC型事務框架搭建與實戰案例【轉】

一、背景 有一定分散式開發經驗的朋友都知道,產品/專案/系統最初為了能夠快速迭代上線,往往不太注重產品/專案/系統的高可靠性、高效能與高擴充套件性,採用單體應用和單例項資料庫的架構方式快速迭代開發;當產品/專案/系統做到一定規模的時候,原有的系統架構則不足以支撐義務發展需要,往往相同的業務則需要

第十講:10.spring事物的支援-程式設計式事務管理

轉賬業務 1,複製spring403-03 改名spring403:建立表結構,資料庫的引擎一定是InnoDB Create Table CREATE TABLE `t_account` (   

MySQL事務-2

在上一篇中我們提到了MySQL的事務特性,這一片主要講述事務的實現。 事務的隔離性由鎖來實現。原子性,一致性,永續性通過資料庫的redo和undo log來實現。 redo恢復提交事務修改頁的操作,而undo回滾行記錄到某個特定版本。因此兩者記錄的內容不同,redo通常是物理日誌,記錄的是頁的物理修改操作

分散式事務MQ可靠訊息

使用MQ可靠訊息能夠解決分散式事務的最終一致性,但不是實時一致(強一致性)。所以使用時要注意應用場景。 MQ可靠訊息: 1.預發訊息:MQ傳送訊息之前把訊息的資訊先存到資料庫中留底,設定一個欄位狀態為待確認。(作用:能夠知道這條訊息是否傳送成功,可進行人工補償) 2.進行業務操作 3

【轉】分散式事務TCC服務設計和實現注意事項

1、TCC簡介 TCC是一種比較成熟的分散式事務解決方案,可用於解決跨庫操作的資料一致性問題; TCC是服務化的兩階段程式設計模型,其Try、Confirm、Cancel 3個方法均由業務編碼實現; 其中Try操作作為一階段,負責資源的檢查和預留,Confirm操作作為二階段提交操作,執行真正的業務,C

分散式事務——基於訊息中介軟體實現

 環境需求:假如某人有5個女朋友(有點複雜),每天晚上都會給他的女朋友打電話說晚安,那麼每給一個女朋友打電話,其他女朋友都要進入等待狀態。一個一個打下去。。。等打到最後一個已經是凌晨了,對方都睡了。那麼有什麼辦法可以解決呢?此時這個人可以利用微信公眾號將自己甜言蜜語放進公眾號

分散式事務本地訊息表

什麼是分散式事務 分散式事務就是指事務的參與者、支援事務的伺服器、資源伺服器以及事務管理器分別位於不同的分散式系統的不同節點之上。簡單的說,就是一次大的操作由不同的小操作組成,這些小的操作分佈在不同的伺服器上,且屬於不同的應用,分散式事務需要保證這些小操作要麼全部成功,要麼全部失敗。本質上來說,分散式事務就

分散式事務可靠訊息

什麼是可靠訊息? 為什麼我們需要它,為什麼我們要強調可靠? 生產方 訊息傳送出去了,如果生產方收到了訊息的正常反饋,那麼我們就可以知道訊息的確切的狀態。 如果訊息無響應 或者超時了呢? 有多個情況, 1 訊息未到達mq,傳送途中 就某些原因丟失了, 2 訊息送達mq,但是mq處理未完成就丟失(這裡又可

什麼是資料庫的事務MySql中哪些儲存引擎支援事務

什麼是事務? 事務由一個或多個sql語句組成一個整體,如果所有的語句執行成功那麼修改將會全部生效,如一條sql語句將銷量+1,下一條再+1,倘若第二條失敗,那麼銷量將撤銷第一條sql語句的+1操作,只有在該事務中所有的語句都執行成功才會將修改加入到資料庫中。

SQL Server的分散式XA事務

XA–eXtended Architecture 在事務中意為分散式事務 XA由協調者(coordinator,一般為transaction manager)和參與者(participants,一般在各個資源上有各自的resource manager)共同完

分散式事務最終一致的Mq實現

問題的起源 分散式系統的特性 對分散式系統有過研究的讀者,可能聽說過“CAP定律”、“Base理論”等,非常巧的是,化學理論中ACID是酸、Base恰好是鹼。這裡我們不對這些概念做過多的解釋,有興趣的讀者可以檢視相關參考資料。 這裡針對一致性我