【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事務是否已經提交,若沒有,則在儲存引擎層再進行一次提交操作。