1. 程式人生 > >【MySQL技術內幕】54-分散式事物

【MySQL技術內幕】54-分散式事物

1、MySQL資料庫分散式事務

InnoDB儲存引擎提供了對XA事務的支援,並通過XA事務來支援分散式事務的實現。分散式事務指的是允許多個獨立的事務資源(transactional resources)參與到一個全域性的事務中。事務資源通常是關係型資料庫系統,但也可以是其他型別的資源。全域性事務要求在其中的所有參與的事務要麼都提交,要麼都回滾,這對於事務原有的ACID要求又有了提高。另外,在使用分散式事務時, InnoDB儲存引擎的事務隔離級別必須設定為SERIALIZABLE。 XA事務允許不同資料庫之間的分散式事務,如一臺伺服器是 MySQL資料庫的,另臺是 Oracle資料庫的,又可能還有一臺伺服器是 SQL Server資料庫的,只要參與在全域性事務中的每個節點都支援XA事務。分散式事務可能在銀行系統的轉賬中比較常見,如使用者Davd需要從上海轉10000元到北京的使用者 Mariah的銀行卡中:

# [email protected]:
UPDATE account SET money=money -10000 WHERE user='David';
# [email protected]
UPDATE account SET money=money+ 10000 WHERE user='Mariah';

在這種情況下,一定需要使用分散式事務來保證資料的安全。如果發生的操作不能全部提交或回滾,那麼任何一個結點出現問題都會導致嚴重的結果。要麼是David的賬戶被扣款,但是 Mariah沒收到,又或者是Davd的賬戶沒有扣款, Mariah卻收到錢了。 XA事務由一個或多個資源管理器(Resource Managers)、一個事務管理器(Transaction Manager)以及一個應用程式(Application Program)組成。

  • 資源管理器:提供訪問事務資源的方法。通常一個數據庫就是一個資源管理器。
  • 事務管理器:協調參與全域性事務中的各個事務。需要和參與全域性事務的所有資源管理器進行通訊。
  • 應用程式:定義事務的邊界,指定全域性事務中的操作。

在 MySQL資料庫的分散式事務中,資源管理器就是 MySQL資料庫,事務管理器為連線 MySQL伺服器的客戶端。圖7-22顯示了一個分散式事務的模型。

分散式事務使用兩段式提交(two- phase commit)的方式。在第一階段,所有參與全域性事務的節點都開始準備( PREPARE),告訴事務管理器它們準備好提交了。在第二階段,事務管理器告訴資源管理器執行 ROLLBACK還是 COMMIT。如果任何一個節點顯示不能提交,則所有的節點都被告知需要回滾。可見與本地事務不同的是,分散式事務需要多一次的 PREPARE操作,待收到所有節點的同意資訊後,再進行 COMMIT或是ROLLBACK操作。 MySQL資料庫XA事務的SQL語法如下:

XA {START I BEGIN} xid [JOIN I RESUME]
XA END xid [ SUSPEND [FOR MIGRATE]]
XA PREPARE xid
XA COMMIT xid [ONE PHASE]
XA ROLLBACK xid
XA RECOVER

在單個節點上執行XA事務的例子:

mysql> XA START 'a';
Query OK, 0 rows affected (0.03 sec)

mysql> insert into t select 11;
Query OK, 1 row affected (0.04 sec)
Records: 1  Duplicates: 0  Warnings: 0

mysql> XA END 'a';
Query OK, 0 rows affected (0.00 sec)

mysql> XA PREPARE 'a';
Query OK, 0 rows affected (0.07 sec)

mysql> XA RECOVER;
+----------+--------------+--------------+------+
| formatID | gtrid_length | bqual_length | data |
+----------+--------------+--------------+------+
|        1 |            1 |            0 | a    |
+----------+--------------+--------------+------+
1 row in set (0.00 sec)

mysql> XA COMMIT 'a';
Query OK, 0 rows affected (0.06 sec)

在單個節點上執行分散式事務沒有太大的實際意義,但是要在 MySQL資料庫的命令下演示多個節點參與的分散式事務也是行不通的。通常來說,都是通過程式語言來完成分散式事務的操作的。當前Java的JTA( Java Transaction API)可以很好地支援MySQL的分散式事務,需要使用分散式事務應該認真參考其AP。下面的一個示例顯示瞭如何使用JTA來呼叫 MySQL的分散式事務,就是前面所舉例的銀行轉賬的例子,程式碼如下,僅供參考:

<dependency>
    <groupId>javax.transaction</groupId>
    <artifactId>jta</artifactId>
    <version>1.1</version>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>6.0.6</version>
</dependency>
public class XaDemo {

    public static MysqlXADataSource getDataSource(String connStr, String user, String pwd) {

        try {

            MysqlXADataSource ds = new MysqlXADataSource();
            ds.setUrl(connStr);
            ds.setUser(user);
            ds.setPassword(pwd);

            return ds;
        } catch (Exception e) {
            e.printStackTrace();
        }

        return null;
    }

    public static void main(String[] arg) {
        String connStr1 = "jdbc:mysql://192.168.0.1:3306/test";
        String connStr2 = "jdbc:mysql://192.168.0.2:3306/test";

        try {
            //從不同資料庫獲取資料庫資料來源
            MysqlXADataSource ds1 = getDataSource(connStr1, "root", "123456");
            MysqlXADataSource ds2 = getDataSource(connStr2, "root", "123456");

            //資料庫1獲取連線
            XAConnection xaConnection1 = ds1.getXAConnection();
            XAResource xaResource1 = xaConnection1.getXAResource();
            Connection connection1 = xaConnection1.getConnection();
            Statement statement1 = connection1.createStatement();

            //資料庫2獲取連線
            XAConnection xaConnection2 = ds2.getXAConnection();
            XAResource xaResource2 = xaConnection2.getXAResource();
            Connection connection2 = xaConnection2.getConnection();
            Statement statement2 = connection2.createStatement();

            //建立事務分支的xid
            Xid xid1 = new MysqlXid(new byte[] { 0x01 }, new byte[] { 0x02 }, 100);
            Xid xid2 = new MysqlXid(new byte[] { 0x011 }, new byte[] { 0x012 }, 100);

            try {
                //事務分支1關聯分支事務sql語句
                xaResource1.start(xid1, XAResource.TMNOFLAGS);
                int update1Result = statement1.executeUpdate("update account_from set money=money - 50 where id=1");
                xaResource1.end(xid1, XAResource.TMSUCCESS);

                //事務分支2關聯分支事務sql語句
                xaResource2.start(xid2, XAResource.TMNOFLAGS);
                int update2Result = statement2.executeUpdate("update account_to set money= money + 50 where id=1");
                xaResource2.end(xid2, XAResource.TMSUCCESS);

                // 兩階段提交協議第一階段
                int ret1 = xaResource1.prepare(xid1);
                int ret2 = xaResource2.prepare(xid2);

                // 兩階段提交協議第二階段
                if (XAResource.XA_OK == ret1 && XAResource.XA_OK == ret2) {
                    xaResource1.commit(xid1, false);
                    xaResource2.commit(xid2, false);

                    System.out.println("reslut1:" + update1Result + ", result2:" + update2Result);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

通過引數 innodb_support_xa可以檢視是否啟用了XA事務的支援(預設為ON):

mysql> SHOW VARIABLES LIKE 'innodb_support_xa';
+-------------------+-------+
| Variable_name     | Value |
+-------------------+-------+
| innodb_support_xa | ON    |
+-------------------+-------+
1 row in set, 1 warning (0.11 sec)

2、內部XA事務

之前討論的分散式事務是外部事務,即資源管理器是 MySQL資料庫本身。在MySQL資料庫中還存在另外一種分散式事務,其在儲存引擎與外掛之間,又或者在儲存引擎與儲存引擎之間,稱之為內部XA事務。 最為常見的內部XA事務存在於binlog與 InnoDB儲存引擎之間。由於複製的需要,因此目前絕大多數的資料庫都開啟了 binlog功能。在事務提交時,先寫二進位制日誌,再寫 InnoDB儲存引擎的重做日誌。對上述兩個操作的要求也是原子的,即二進位制日誌和重做日誌必須同時寫入。若二進位制日誌先寫了,而在寫人 InnoDB儲存引擎時發生了宕機,那麼 slave可能會接收到 master傳過去的二進位制日誌並執行,最終導致了主從不一致的情況。如圖7-23所示。 在圖7-23中,如果執行完①、②後在步驟③之前 MySQL資料庫發生了宕機,則會發生主從不一致的情況。為了解決這個問題, MySQL資料庫在 binlog與InnoDB儲存引擎之間採用XA事務。當事務提交時, InnoDB儲存引擎會先做一個PREPARE操作,將事務的xid寫人,接著進行二進位制日誌的寫入,如圖7-24所示。如果在 InnoDB儲存引擎提交前, MySQL資料庫宕機了,那麼 MySQL資料庫在重啟後會先檢查準備的UXID事務是否已經提交,若沒有,則在儲存引擎層再進行一次提交操作。