1. 程式人生 > 其它 >資料庫中介軟體 MyCAT原始碼分析 —— XA分散式事務

資料庫中介軟體 MyCAT原始碼分析 —— XA分散式事務


  • 1. 概述
  • 2. XA 概念
  • 3. MyCAT 程式碼實現
    • 3.1 JDBC Demo 程式碼
    • 3.2 MyCAT 開啟 XA 事務
    • 3.3 MyCAT 接收 SQL
    • 3.4 MySQL 接收 COMMIT
      • 3.4.1 單節點事務 or 多節點事務
      • 3.4.2 協調日誌
      • 3.4.3 MultiNodeCoordinator
      • 3.5 MyCAT 啟動回滾 XA事務
  • 4. MyCAT 實現缺陷
    • 4.1 協調日誌寫入效能
    • 4.2 資料節點未全部 PREPARE 就進行 COMMIT
    • 4.3 MyCAT 啟動回滾 PREPARE 的 XA事務
    • 4.4 單節點事務未記錄協調日誌
    • 4.5 XA COMMIT 部分節點掛了重新恢復後,未進一步處理

1. 概述

資料庫拆分後,業務上會碰到需要分散式事務的場景。MyCAT 基於 XA 實現分散式事務。國內目前另外一款很火的資料庫中介軟體 Sharding-JDBC 準備基於 TCC 實現分散式事務。

本文內容分成三部分:

  1. XA 概念簡述
  2. MyCAT 程式碼如何實現 XA
  3. MyCAT 在實現 XA 存在的一些缺陷

2. XA 概念

> X/Open 組織(即現在的 Open Group )定義了分散式事務處理模型。 X/Open DTP 模型( 1994 )包括:

  1. 應用程式( AP
  2. 事務管理器( TM
  3. 資源管理器( RM
  4. 通訊資源管理器( CRM ) 一般,常見的事務管理器( TM )是交易中介軟體,常見的資源管理器( RM
    )是資料庫,常見的通訊資源管理器( CRM)是訊息中介軟體,下圖是X/Open DTP模型:

一般的程式設計方式是這樣的:

  1. 配置 TM ,通過 TM 或者 RM 提供的方式,把 RM 註冊到 TM。可以理解為給 TM 註冊 RM 作為資料來源。一個 TM 可以註冊多個 RM
  2. APTM 獲取資源管理器的代理(例如:使用JTA介面,從TM管理的上下文中,獲取出這個TM所管理的RM的JDBC連線或JMS連線) APTM 發起一個全域性事務。這時,TM 會通知各個 RMXID(全域性事務ID)會通知到各個RM。
  3. AP 通過 TM 中獲取的連線,間接操作 RM 進行業務操作。這時,TM
    在每次 AP 操作時把 XID(包括所屬分支的資訊)傳遞給 RMRM 正是通過這個 XID 關聯來操作和事務的關係的。
  4. AP 結束全域性事務時,TM 會通知 RM 全域性事務結束。開始二段提交,也就是prepare - commit的過程。

XA協議指的是TM(事務管理器)和RM(資源管理器)之間的介面。目前主流的關係型資料庫產品都是實現了XA介面的。JTA(Java Transaction API)是符合X/Open DTP模型的,事務管理器和資源管理器之間也使用了XA協議。 本質上也是藉助兩階段提交協議來實現分散式事務的,下面分別來看看XA事務成功和失敗的模型圖:


? 看到這裡是不是有種黑人問號的感覺?淡定!我們接下來看 MyCAT 程式碼層面是如何實現 XA 的。另外,有興趣對概念瞭解更多的,可以參看如下文章:

  1. 《XA事務處理》
  2. 《XA Transaction SQL Syntax》
  3. 《MySQL XA 事務支援調研》

3. MyCAT 程式碼實現

  • MyCAT :TM,協調者。
  • 資料節點 :RM,參與者。

3.1 JDBC Demo 程式碼

public class MyCATXAClientDemo {

    public static void main(String[] args) throws ClassNotFoundException, SQLException {
        // 1. 獲得資料庫連線
        Class.forName("com.mysql.jdbc.Driver");
        Connection conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1:8066/dbtest", "root", "123456");
        conn.setAutoCommit(false);

        // 2. 開啟 MyCAT XA 事務
        conn.prepareStatement("set xa=on").execute();

        // 3. 插入 SQL
        // 3.1 SQL1 A庫
        long uid = Math.abs(new Random().nextLong());
        String username = UUID.randomUUID().toString();
        String password = UUID.randomUUID().toString();
        String sql1 = String.format("insert into t_user(id, username, password) VALUES (%d, '%s', '%s')",
                uid, username, password);
        conn.prepareStatement(sql1).execute();
        // 3.2 SQL2 B庫
        long orderId = Math.abs(new Random().nextLong());
        String nickname = UUID.randomUUID().toString();
        String sql2 = String.format("insert into t_order(id, uid, nickname) VALUES(%d, %s, '%s')", orderId, uid, nickname);
        conn.prepareStatement(sql2).execute();

        // 4. 提交 XA 事務
        conn.commit();
    }

}
  • setxa=on MyCAT 開啟 XA 事務。
  • conn.commit 提交 XA 事務。

3.2 MyCAT 開啟 XA 事務

當 MyCAT 接收到 setxa=on 命令時,開啟 XA 事務,並生成 XA 事務編號。XA 事務編號生成演算法為 UUID。核心程式碼如下:

// SetHandler.java
public static void handle(String stmt, ServerConnection c, int offset) {
        int rs = ServerParseSet.parse(stmt, offset);
        switch (rs & 0xff) {
        // ... 省略程式碼
        case XA_FLAG_ON: {
            if (c.isAutocommit()) {
                c.writeErrMessage(ErrorCode.ERR_WRONG_USED, "set xa cmd on can't used in autocommit connection ");
                return;
            }
            c.getSession2().setXATXEnabled(true);
            c.write(c.writeToBuffer(OkPacket.OK, c.allocate()));
            break;
        }
        case XA_FLAG_OFF: {
            c.writeErrMessage(ErrorCode.ERR_WRONG_USED,
                    "set xa cmd off not for external use ");
            return;
        }
        // ... 省略程式碼
    }
}
// NonBlockingSession.java
public void setXATXEnabled(boolean xaTXEnabled) {
   if (xaTXEnabled) {
       if (this.xaTXID == null) {
           xaTXID = genXATXID(); // ???獲得 XA 事務編號
       }
   } else {
       this.xaTXID = null;
   }
}
private String genXATXID() {
   return MycatServer.getInstance().getXATXIDGLOBAL();
}
// MycatServer.java
public String getXATXIDGLOBAL() {
   return "'" + getUUID() + "'";
}
public static String getUUID() { // ???
   String s = UUID.randomUUID().toString();
   return s.substring(0, 8) + s.substring(9, 13) + s.substring(14, 18) + s.substring(19, 23) + s.substring(24);
}

3.3 MyCAT 接收 SQL

此處 SQL 指的是 insertupdatedelete 操作。

當向某個資料節點第一次發起 SQL 時,會在 SQL 前面附加 XA START'xaTranId',並設定該資料節點連線事務狀態為 TxState.TX_STARTED_STATE(分散式事務狀態,下文會專門整理)。核心程式碼如下:

// MySQLConnection.java
private void synAndDoExecute(String xaTxID, RouteResultsetNode rrn,
                                 int clientCharSetIndex, int clientTxIsoLation,
                                 boolean clientAutoCommit) {
   String xaCmd = null;
   boolean conAutoComit = this.autocommit;
   String conSchema = this.schema;
   // never executed modify sql,so auto commit
   boolean expectAutocommit = !modifiedSQLExecuted || isFromSlaveDB() || clientAutoCommit;
   if (expectAutocommit == false && xaTxID != null && xaStatus == TxState.TX_INITIALIZE_STATE) { // ???
       xaCmd = "XA START " + xaTxID + ';';
       this.xaStatus = TxState.TX_STARTED_STATE;
   }
   // .... 省略程式碼
   StringBuilder sb = new StringBuilder();
   // .... 省略程式碼
   if (xaCmd != null) {
       sb.append(xaCmd);
   }
   // and our query sql to multi command at last
   sb.append(rrn.getStatement() + ";");
   // syn and execute others
   this.sendQueryCmd(sb.toString());
}

舉個 變數 sb 的例子:

SET names utf8;SET autocommit=0;XA START '1f2da7353e8846e5833b8d8dd041cfb1','db2';insert into t_user(id, username, password) VALUES (3400, 'b7c5ec1f-11cc-4599-851c-06ad617fec42', 'd2694679-f6a2-4623-a339-48d4a868be90');

3.4 MySQL 接收 COMMIT

3.4.1 單節點事務 or 多節點事務

COMMIT 執行時,MyCAT 會判斷 XA 事務裡,涉及到的資料庫節點數量。

  • 如果節點數量為 1,單節點事務,使用 CommitNodeHandler 處理。
  • 如果節點數量 > 1,多節點事務,使用 MultiNodeCoordinator 處理。

CommitNodeHandler 相比 MultiNodeCoordinator 來說,只有一個數據節點,不需要進行多節點協調,邏輯會相對簡單,有興趣的同學可以另外看。我們主要分析 MultiNodeCoordinator

3.4.2 協調日誌

協調日誌,記錄協調過程中各資料節點 XA 事務狀態,處理MyCAT異常奔潰或者資料節點部分XA COMMIT,另外部分 XA PREPARE下的狀態恢復。

XA 事務共有種

  1. TXINITIALIZESTATE :事務初始化
  2. TXSTARTEDSTATE :事務開始完成
  3. TXPREPAREDSTATE :事務準備完成
  4. TXCOMMITEDSTATE :事務提交完成
  5. TXROLLBACKEDSTATE :事務回滾完成

狀態變更流 :TXINITIALIZESTATE => TXSTARTEDSTATE => TXPREPAREDSTATE => TXCOMMITEDSTATE / TXROLLBACKEDSTATE 。

協調日誌包含兩個部分

  1. CoordinatorLogEntry :協調者日誌
  2. ParticipantLogEntry :參與者日誌。此處,資料節點扮演參與者的角色。下文中,可能會出現參與者與資料節點混用的情況,望見諒。

一次 XA 事務,對應一條 CoordinatorLogEntry。一條 CoordinatorLogEntry 包含 N條 ParticipantLogEntry。 核心程式碼如下:

// CoordinatorLogEntry :協調者日誌
public class CoordinatorLogEntry implements Serializable {

    /**
     * XA 事務編號
     */
    public final String id;
    /**
     * 參與者日誌陣列
     */
    public final ParticipantLogEntry[] participants;
}
// ParticipantLogEntry :參與者日誌
public class ParticipantLogEntry implements Serializable {

    /**
     * XA 事務編號
     */
    public String coordinatorId;
    /**
     * 資料庫 uri
     */
    public String uri;
    /**
     * 過期描述
     */
    public long expires;
    /**
     * XA 事務狀態
     */
    public int txState;
    /**
     * 參與者名字
     */
    public String resourceName;
}

MyCAT 記錄協調日誌以 JSON格式 到檔案每行包含一條 CoordinatorLogEntry。舉個例子:

{"id":"'e827b3fe666c4d968961350d19adda31'","participants":[{"uri":"127.0.0.1","state":"3","expires":0,"resourceName":"db3"},{"uri":"127.0.0.1","state":"3","expires":0,"resourceName":"db1"}]}
{"id":"'f00b61fa17cb4ec5b8264a6d82f847d0'","participants":[{"uri":"127.0.0.1","state":"3","expires":0,"resourceName":"db2"},{"uri":"127.0.0.1","state":"3","expires":0,"resourceName":"db1"}]}

實現類為:

// XA 協調者日誌 儲存介面:https://github.com/YunaiV/Mycat-Server/blob/1.6/src/main/java/io/mycat/backend/mysql/xa/recovery/Repository.java
public interface Repository {}
// XA 協調者日誌 檔案儲存:https://github.com/YunaiV/Mycat-Server/blob/1.6/src/main/java/io/mycat/backend/mysql/xa/recovery/impl/FileSystemRepository.java
public class FileSystemRepository implements Repository {}
// XA 協調者日誌 檔案儲存:https://github.com/YunaiV/Mycat-Server/blob/1.6/src/main/java/io/mycat/backend/mysql/xa/recovery/impl/InMemoryRepository.java
public class InMemoryRepository implements Repository {}

目前日誌檔案寫入的方式效能較差,這裡我們不做分析,在【4. MyCAT 實現缺陷】裡一起講。

3.4.3 MultiNodeCoordinator

敲敲敲,這裡是本文的重點之一噢。?

第一階段:發起 PREPARE。

public void executeBatchNodeCmd(SQLCtrlCommand cmdHandler) {
   this.cmdHandler = cmdHandler;
   final int initCount = session.getTargetCount();
   runningCount.set(initCount);
   nodeCount = initCount;
   failed.set(false);
   faileCount.set(0);
   //recovery nodes log
   ParticipantLogEntry[] participantLogEntry = new ParticipantLogEntry[initCount];
   // 執行
   int started = 0;
   for (RouteResultsetNode rrn : session.getTargetKeys()) {
       if (rrn == null) {
           continue;
       }
       final BackendConnection conn = session.getTarget(rrn);
       if (conn != null) {
           conn.setResponseHandler(this);
           //process the XA_END XA_PREPARE Command
           MySQLConnection mysqlCon = (MySQLConnection) conn;
           String xaTxId = null;
           if (session.getXaTXID() != null) {
               xaTxId = session.getXaTXID() + ",'" + mysqlCon.getSchema() + "'";
           }
           if (mysqlCon.getXaStatus() == TxState.TX_STARTED_STATE) { // XA 事務
               //recovery Log
               participantLogEntry[started] = new ParticipantLogEntry(xaTxId, conn.getHost(), 0, conn.getSchema(), ((MySQLConnection) conn).getXaStatus());
               String[] cmds = new String[]{"XA END " + xaTxId, // XA END 命令
                       "XA PREPARE " + xaTxId}; // XA PREPARE 命令
               mysqlCon.execBatchCmd(cmds);
           } else { // 非 XA 事務
               // recovery Log
               participantLogEntry[started] = new ParticipantLogEntry(xaTxId, conn.getHost(), 0, conn.getSchema(), ((MySQLConnection) conn).getXaStatus());
               cmdHandler.sendCommand(session, conn);
           }
           ++started;
       }
   }
   // xa recovery log
   if (session.getXaTXID() != null) {
       CoordinatorLogEntry coordinatorLogEntry = new CoordinatorLogEntry(session.getXaTXID(), false, participantLogEntry);
       inMemoryRepository.put(session.getXaTXID(), coordinatorLogEntry);
       fileRepository.writeCheckpoint(inMemoryRepository.getAllCoordinatorLogEntries());
   }
   if (started < nodeCount) { // TODO 疑問:如何觸發
       runningCount.set(started);
       LOGGER.warn("some connection failed to execute " + (nodeCount - started));
       /**
        * assumption: only caused by front-end connection close. <br/>
        * Otherwise, packet must be returned to front-end
        */
       failed.set(true);
   }
}
  • 向各資料節點發送 XAEND + XA PREPARE 指令。舉個 變數 cmds 例子:
XA END '4cbb18214d0b47adbdb0658598666677','db3';XA PREPARE '4cbb18214d0b47adbdb0658598666677','db3';
  • 記錄協調日誌。每條參與者日誌狀態為 TxState.TX_STARTED_STATE

第二階段:發起 COMMIT。

@Override
public void okResponse(byte[] ok, BackendConnection conn) {
   // process the XA Transatcion 2pc commit
   if (conn instanceof MySQLConnection) {
       MySQLConnection mysqlCon = (MySQLConnection) conn;
       switch (mysqlCon.getXaStatus()) {
           case TxState.TX_STARTED_STATE:
               //if there have many SQL execute wait the okResponse,will come to here one by one
               //should be wait all nodes ready ,then send xa commit to all nodes.
               if (mysqlCon.batchCmdFinished()) {
                   String xaTxId = session.getXaTXID();
                   String cmd = "XA COMMIT " + xaTxId + ",'" + mysqlCon.getSchema() + "'";
                   if (LOGGER.isDebugEnabled()) {
                       LOGGER.debug("Start execute the cmd :" + cmd + ",current host:" + mysqlCon.getHost() + ":" + mysqlCon.getPort());
                   }
                   // recovery log
                   CoordinatorLogEntry coordinatorLogEntry = inMemoryRepository.get(xaTxId);
                   for (int i = 0; i < coordinatorLogEntry.participants.length; i++) {
                       LOGGER.debug("[In Memory CoordinatorLogEntry]" + coordinatorLogEntry.participants[i]);
                       if (coordinatorLogEntry.participants[i].resourceName.equals(conn.getSchema())) {
                           coordinatorLogEntry.participants[i].txState = TxState.TX_PREPARED_STATE;
                       }
                   }
                   inMemoryRepository.put(xaTxId, coordinatorLogEntry);
                   fileRepository.writeCheckpoint(inMemoryRepository.getAllCoordinatorLogEntries());
                   // send commit
                   mysqlCon.setXaStatus(TxState.TX_PREPARED_STATE);
                   mysqlCon.execCmd(cmd);
               }
               return;
           case TxState.TX_PREPARED_STATE: {
               // recovery log
               String xaTxId = session.getXaTXID();
               CoordinatorLogEntry coordinatorLogEntry = inMemoryRepository.get(xaTxId);
               for (int i = 0; i < coordinatorLogEntry.participants.length; i++) {
                   if (coordinatorLogEntry.participants[i].resourceName.equals(conn.getSchema())) {
                       coordinatorLogEntry.participants[i].txState = TxState.TX_COMMITED_STATE;
                   }
               }
               inMemoryRepository.put(xaTxId, coordinatorLogEntry);
               fileRepository.writeCheckpoint(inMemoryRepository.getAllCoordinatorLogEntries());
               // XA reset status now
               mysqlCon.setXaStatus(TxState.TX_INITIALIZE_STATE);
               break;
           }
           default:
       }
   }
   // 釋放連線
   if (this.cmdHandler.relaseConOnOK()) {
       session.releaseConnection(conn);
   } else {
       session.releaseConnectionIfSafe(conn, LOGGER.isDebugEnabled(), false);
   }
   // 是否所有節點都完成commit,如果是,則返回Client 成功
   if (this.finished()) {
       cmdHandler.okResponse(session, ok);
       if (cmdHandler.isAutoClearSessionCons()) {
           session.clearResources(false);
       }
       /* 1.  事務提交後,xa 事務結束   */
       if (session.getXaTXID() != null) {
           session.setXATXEnabled(false);
       }
       /* 2. preAcStates 為true,事務結束後,需要設定為true。preAcStates 為ac上一個狀態    */
       if (session.getSource().isPreAcStates()) {
           session.getSource().setAutocommit(true);
       }
   }
}
  • mysqlCon.batchCmdFinished() 每個資料節點,第一次返回的是 XAEND 成功,第二次返回的是 XA PREPARE。在 XA PREPARE 成功後,記錄該資料節點的參與者日誌狀態為 TxState.TX_PREPARED_STATE。之後,向該資料節點發起 XA COMMIT 命令。
  • XA COMMIT 返回成功後,記錄該資料節點的事務參與者日誌狀態為 TxState.TX_COMMITED_STATE
  • 當所有資料節點(參與者)都執行完成 XA COMMIT 返回,即 this.finished()==true,返回 MySQL Client XA 事務提交成功。

[x] XA PREPAREXA COMMIT,資料節點可能返回失敗,目前暫時沒模擬出來,對應方法為 #errorResponse(....)

3.5 MyCAT 啟動回滾 XA事務

MyCAT 啟動時,會回滾處於TxState.TXPREPAREDSTATEParticipantLogEntry 對應的資料節點的 XA 事務。程式碼如下:

// MycatServer.java
private void performXARecoveryLog() {
   // fetch the recovery log
   CoordinatorLogEntry[] coordinatorLogEntries = getCoordinatorLogEntries();
   for (int i = 0; i < coordinatorLogEntries.length; i++) {
       CoordinatorLogEntry coordinatorLogEntry = coordinatorLogEntries[i];
       boolean needRollback = false;
       for (int j = 0; j < coordinatorLogEntry.participants.length; j++) {
           ParticipantLogEntry participantLogEntry = coordinatorLogEntry.participants[j];
           if (participantLogEntry.txState == TxState.TX_PREPARED_STATE) {
               needRollback = true;
               break;
           }
       }
       if (needRollback) {
           for (int j = 0; j < coordinatorLogEntry.participants.length; j++) {
               ParticipantLogEntry participantLogEntry = coordinatorLogEntry.participants[j];
               //XA rollback
               String xacmd = "XA ROLLBACK " + coordinatorLogEntry.id + ';';
               OneRawSQLQueryResultHandler resultHandler = new OneRawSQLQueryResultHandler(new String[0], new XARollbackCallback());
               outloop:
               for (SchemaConfig schema : MycatServer.getInstance().getConfig().getSchemas().values()) {
                   for (TableConfig table : schema.getTables().values()) {
                       for (String dataNode : table.getDataNodes()) {
                           PhysicalDBNode dn = MycatServer.getInstance().getConfig().getDataNodes().get(dataNode);
                           if (dn.getDbPool().getSource().getConfig().getIp().equals(participantLogEntry.uri)
                                   && dn.getDatabase().equals(participantLogEntry.resourceName)) {
                               //XA STATE ROLLBACK
                               participantLogEntry.txState = TxState.TX_ROLLBACKED_STATE;
                               SQLJob sqlJob = new SQLJob(xacmd, dn.getDatabase(), resultHandler, dn.getDbPool().getSource());
                               sqlJob.run();
                               break outloop;
                           }
                       }
                   }
               }
           }
       }
   }
   // init into in memory cached
   for (int i = 0; i < coordinatorLogEntries.length; i++) {
  MultiNodeCoordinator.inMemoryRepository.put(coordinatorLogEntries[i].id, coordinatorLogEntries[i]);
   }
   // discard the recovery log
    MultiNodeCoordinator.fileRepository.writeCheckpoint(MultiNodeCoordinator.inMemoryRepository.getAllCoordinatorLogEntries());
}

4. MyCAT 實現缺陷

MyCAT 1.6.5 版本實現弱XA事務,相對來說,筆者認為距離實際生產使用存在一些差距。下面羅列可能存在的缺陷,如有錯誤,麻煩指出。?希望 MyCAT 在分散式事務的實現上,能夠越來越給力。

4.1 協調日誌寫入效能

1、 CoordinatorLogEntryParticipantLogEntry 在每次寫入檔案時,是將記憶體中所有的日誌全部重新寫入,導致寫入效能隨著 XA 事務次數的增加,效能會越來越糟糕,導致 XA 事務整體效能會非常差。另外,該方法是同步的,也加大了寫入的延遲。

建議:先獲得可寫入檔案的 OFFSET,寫入協調日誌到檔案,記憶體維護好 XA事務編號 與 OFFSET 的對映關係,從而實現順序寫入 + 並行寫入

2、記憶體裡維護了所有的協調日誌,佔用記憶體會越來越大,並且無釋放機制。即使重啟,協調日誌也會重新載入到記憶體。

建議:已完全回滾或者提交的協調日誌不放入記憶體。另外有檔案儲存好 XA事務編號 與 OFFSET 的對映關係。

3、協調日誌只寫入單個檔案。

建議:分拆協調日誌檔案。

PS:有興趣的同學可以看下 RocketMQCommitLog 的儲存,效能上很贊!

4.2 資料節點未全部 PREPARE 就進行 COMMIT

XA 事務定義,需要等待所有參與者全部 XA PREPARE 成功完成後發起 XA COMMIT。目前 MyCAT 是某個資料節點 XA PREPARE 完成後立即進行 XA COMMIT。比如說:第一個資料節點提交了 XAEND;XA PREPARE 時,第二個資料節在進行 XAEND;XA PREAPRE; 前掛了,第一個節點依然會 XA COMMIT 成功。

建議:按照嚴格的 XA 事務定義。

4.3 MyCAT 啟動回滾 PREPARE 的 XA事務

1、MyCAT 啟動時,回滾所有的 PREPARE 的 XA 事務,可能某個 XA 事務,部分 COMMIT,部分 PREPARE。此時直接回滾,會導致資料不一致。

建議:當判斷到某個 XA 事務存在 PREPARE 的參與者,同時判斷該 XA 事務裡其他參與者的事務狀態以及資料節點裡 XA 事務狀態,比如參與者為 MySQL時,可以使用 XA RECOVER 查詢處於 PREPARE 所有的 XA 事務。

2、回滾 PREPARE 是非同步進行的,在未進行完成時已經設定檔案裡回滾成功。如果非同步過程中失敗,會導致 XA 事務狀態不一致。

建議:回撥成功後,更新該 XA 事務狀態。

4.4 單節點事務未記錄協調日誌

該情況較為極端。發起 XA PREPARE完後,MyCAT 掛了。重啟後,該 XA 事務在 MyCAT 裡就“消失“了,參與者的該 XA 事務一直處於 PREPARE 狀態。從理論上來說,需要回滾該 XA 事務。

建議:記錄協調日誌。

4.5 XA COMMIT 部分節點掛了重新恢復後,未進一步處理

當一部分節點 XA COMMIT 完成,另外一部分此時掛了。在管理員重啟掛掉的節點,其對應的 XA 事務未進一步處理,導致資料不一致。。