深入解析:MySQL對分佈式事務 XA Transactions 的支持

原文鏈接:https://www.modb.pro/db/23673 (複製鏈接至瀏覽器,即可查看)


摘要:MySQL對分佈式事務(XA Transactions)進行了很好的支持,我們看看它是怎麼做的,並實戰驗證其提供的分佈式事務控制語句效果。

MySQL從5.0.3開始,InnoDB存儲引擎支持XA事務(XA Transactions)。MySQL XA是基於X/Open CAE文檔中的Distributed Transaction Processing:The XA Specification(DTP XA規範)實現的。

  • X/Open是一個獨立的、全球性的開放系統組織,由世界上最大的信息系統供應商、用戶組織和軟件公司支持。其使命是通過開放系統的實際實施,為用戶帶來更大的計算價值。
  • X/Open CAE規範,即X/Open Common Applications Environment,這個環境覆蓋了高於硬件級別的,支持開放系統所需的一組標準。它提供了應用程序的可移植性和互操作性。

一個分佈式事務會涉及多個行動,這些行動本身是事務性的。所有行動都必須一起成功完成,或者一起被回滾。

在MySQL中,使用分佈式事務涉及一個或多個資源管理器和一個事務管理器。

  • 資源管理器(RM)用於提供通向事務資源的途徑。數據庫服務器是一種資源管理器,該管理器必須可以提交或者回滾由RM管理的事務。
  • 事務管理器(TM)用於協調作為一個分佈式事務一部分的事務。TM與管理每個事務的RMs進行通信。在一個分佈式事務中,各個單個事務均是分佈式事務的“分支事務”。分佈式事務和各分支通過一種命名方法進行標識。

執行XA事務時,MySQL服務器相當於一個用於管理分佈式事務的XA事務資源管理器。與MySQL服務器連接的客戶端相當於事務管理器。

要執行一個分佈式事務,必須知道這個分佈式事務涉及了哪些資源管理器,並且把每個資源管理器的事務執行到事務可以被提交或者回滾。根據每個資源管理器報告的執行情況,這些分支事務必須作為一個原子性操作全部提交或回滾。要管理一個分佈式事務,必須考慮任務組件或者網絡可能出現故障。

用於執行分佈式事務的過程稱為兩階段提交,發生時間在由分佈式事務的各個分支需要進行的行動已經被執行之後。

  • 在第一階段,所有的分支被預備好。即它們被TM告知要準備提交。
  • 在第二階段,TM告知RMs是否要提交或回滾。如果在預備分支時,所有的分值指示它們將能夠提交,則所有的分支被告知要提交。如果在預備時,有任何分支指示它將不能提交,則所有分支被告知回滾。

有些情況下,一個分佈式事務可能使用一階段提交。比如,當一個事務管理器發現,一個分佈式事務只由一個事務資源組成(即一個分支),則資源可以被告知同時進行預備和提交。

在MySQL 5.7中,分佈式事務的語法如下:

<code>XA {START|BEGIN} xid [JOIN|RESUME]

XA END xid [SUSPEND [FOR MIGRATE]]

XA PREPARE xid

XA COMMIT xid [ONE PHASE]

XA ROLLBACK xid

XA RECOVER [CONVERT XID]
/<code>

每個分佈式事務語句都以XA關鍵字開頭,並且大多數語句都需要xid值。xid是XA事務標識符,它表示語句操作哪個事務。xid值由客戶端提供,或由MySQL服務器生成。xid值由1到3部分組成:

<code>xid: gtrid [, bqual [, formatID ]]
/<code>
  • gtrid是全局事務標識符。
  • bqual是分支限定符,默認值是’’,對於分佈式事務中的每個分支事務,bqual值必須是唯一的。
  • formatID是標識gtrid和bqual值使用的格式,默認值為1。

XA START xid使用給定的xid值啟動XA事務。每個XA事務必須具有唯一的xid值,該值當前不能由另一個XA事務使用。其它命令就是分別實現不同的XA事務控制,參考下圖:

深入解析:MySQL對分佈式事務 XA Transactions 的支持

XA RECOVER返回當前數據庫中處於PREPARE狀態的分支事務的詳細信息。

分佈式的關鍵在於如何確保分佈式事務的完整性,以及在某個分支出現問題時的故障解決。XA的相關命令就是提供給應用在多個獨立的數據庫之間進行分佈式事務的管理。

我們人工模擬個場景,實戰下MySQL對分佈式事務語句的支持。假設肖傑在某銀行有一個儲蓄賬戶和一個理財賬戶,儲蓄賬戶的相關信息被存儲在cash數據庫中,理財賬戶相關信息被存儲在financing數據庫中,他現在要從儲蓄賬戶往理財賬戶轉3000塊錢,本質上就是在兩個數據庫中更新記錄,但兩個操作需作為同一個事務提交或回滾。

創建基礎環境:

<code>root@database-one 21:19:  [(none)]> create database cash_gftest default charset utf8;
Query OK, 1 row affected (0.02 sec)

root@database-one 21:25: [(none)]> use cash_gftest;
Database changed
root@database-one 21:25: [cash_gftest]> create table cash_account(name varchar(10),balance decimal(10,2)) engine=innodb;
Query OK, 0 rows affected (0.05 sec)

root@database-one 21:25: [cash_gftest]> insert into cash_account values('郭佳',10000),('劉強',8000),('肖傑',22000);
Query OK, 3 rows affected (0.00 sec)
Records: 3 Duplicates: 0 Warnings: 0

root@database-one 21:29: [cash_gftest]> create database financing_gftest default charset utf8;
Query OK, 1 row affected (0.00 sec)

root@database-one 21:29: [cash_gftest]> use financing_gftest

Database changed
root@database-one 21:29: [financing_gftest]> create table financing_account(name varchar(10),balance decimal(10,2)) engine=innodb;
Query OK, 0 rows affected (0.08 sec)

root@database-one 21:30: [financing_gftest]> insert into financing_account values('郭佳',0),('劉強',0),('肖傑',0);
Query OK, 3 rows affected (0.00 sec)
Records: 3 Duplicates: 0 Warnings: 0

root@database-one 21:30: [financing_gftest]> select * from financing_gftest.financing_account;
+--------+---------+
| name | balance |
+--------+---------+
| 郭佳 | 0.00 |
| 劉強 | 0.00 |
| 肖傑 | 0.00 |
+--------+---------+
3 rows in set (0.00 sec)

root@database-one 21:31: [financing_gftest]> select * from cash_gftest.cash_account;
+--------+----------+
| name | balance |
+--------+----------+
| 郭佳 | 10000.00 |
| 劉強 | 8000.00 |
| 肖傑 | 22000.00 |
+--------+----------+
3 rows in set (0.00 sec)
/<code>

創建會話1,連入cash數據庫,模擬儲蓄賬戶應用程序。

<code>root@database-one 21:36:  [(none)]> prompt \\\\u@database-one \\R:\\m:\\s [\\d] session1>
PROMPT set to '\\\\u@database-one \\R:\\m:\\s [\\d] session1>'
root@database-one 21:36:33 [(none)] session1>use cash_gftest;
Database changed
root@database-one 21:36:49 [cash_gftest] session1>select * from cash_account;
+--------+----------+
| name | balance |
+--------+----------+
| 郭佳 | 10000.00 |
| 劉強 | 8000.00 |
| 肖傑 | 22000.00 |
+--------+----------+
3 rows in set (0.01 sec)
/<code>

創建會話2,連入financing數據庫,模擬理財賬戶應用程序。

<code>root@database-one 21:38:  [(none)]> prompt \\\\u@database-one \\R:\\m:\\s [\\d] session2>
PROMPT set to '\\\\u@database-one \\R:\\m:\\s [\\d] session2>'
root@database-one 21:38:38 [(none)] session2>use financing_gftest;
Database changed
root@database-one 21:38:50 [financing_gftest] session2>select * from financing_account;
+--------+---------+
| name | balance |
+--------+---------+
| 郭佳 | 0.00 |
| 劉強 | 0.00 |
| 肖傑 | 0.00 |
+--------+---------+
3 rows in set (0.00 sec)
/<code>

肖傑開始轉賬,在cash庫中啟動一個分佈式事務的一個分支,xid的gtrid為“transfer_of_account”,bqual為“cash”

<code>root@database-one 21:45:09 [cash_gftest] session1>xa start 'transfer_of_account','cash';
Query OK, 0 rows affected (0.01 sec)

root@database-one 21:45:43 [cash_gftest] session1>update cash_account set balance=balance-3000 where name='肖傑';
Query OK, 1 row affected (0.01 sec)
Rows matched: 1 Changed: 1 Warnings: 0

root@database-one 21:46:50 [cash_gftest] session1>xa end 'transfer_of_account','cash';
Query OK, 0 rows affected (0.01 sec)

root@database-one 21:48:17 [cash_gftest] session1>xa prepare 'transfer_of_account','cash';
Query OK, 0 rows affected (0.00 sec)
/<code>

可以看到在對肖傑儲蓄賬戶減去3000元后,將分支事務“cash”進行第一階段提交,進入prepare狀態。

在financing庫中啟動分佈式事務“transfer_of_account”的另一個分支,bqual為“financing”

<code>root@database-one 21:54:24 [financing_gftest] session2>xa start 'transfer_of_account','financing'; 

Query OK, 0 rows affected (0.00 sec)

root@database-one 21:54:51 [financing_gftest] session2>update financing_account set balance=balance+3000 where name='肖傑';
Query OK, 1 row affected (0.06 sec)
Rows matched: 1 Changed: 1 Warnings: 0

root@database-one 21:55:28 [financing_gftest] session2>xa end 'transfer_of_account','financing';
Query OK, 0 rows affected (0.02 sec)

root@database-one 21:56:03 [financing_gftest] session2>xa prepare 'transfer_of_account','financing';
Query OK, 0 rows affected (0.02 sec)
/<code>

可以看到在對肖傑理財賬戶增加3000元后,將分支事務“financing”進行第一階段提交,進入prepare狀態。

分別在兩個庫中用xa recover查看當前分支事務狀態

<code>root@database-one 22:04:50 [cash_gftest] session1>xa recover;
+----------+--------------+--------------+------------------------------+
| formatID | gtrid_length | bqual_length | data |
+----------+--------------+--------------+------------------------------+
| 1 | 19 | 4 | transfer_of_accountcash |
| 1 | 19 | 9 | transfer_of_accountfinancing |
+----------+--------------+--------------+------------------------------+
2 rows in set (0.00 sec)
/<code>
<code>root@database-one 22:06:05 [financing_gftest] session2>xa recover;
+----------+--------------+--------------+------------------------------+
| formatID | gtrid_length | bqual_length | data |
+----------+--------------+--------------+------------------------------+
| 1 | 19 | 4 | transfer_of_accountcash |
| 1 | 19 | 9 | transfer_of_accountfinancing |
+----------+--------------+--------------+------------------------------+
2 rows in set (0.01 sec)
/<code>

兩個會話都能看到兩個分支事務的信息,這是因為這兩個庫是在同一個MySQL服務中,如果是位於不同的MySQL服務中,將只能看到各自的分支事務信息。

如果某個分支事務在進入到prepare之前,遇到任何錯誤,就可以回滾其它分支事務,確保分佈式事務的正確。

我們這裡操作正常,對所有分支事務均提交,徹底完成整個轉賬動作。

<code>root@database-one 22:12:43 [cash_gftest] session1>xa commit 'transfer_of_account','cash';
Query OK, 0 rows affected (0.01 sec)
/<code>
<code>root@database-one 22:13:50 [financing_gftest] session2>xa commit 'transfer_of_account','financing';
Query OK, 0 rows affected (0.00 sec)
/<code>

通過驗證可以發現,MySQL確實很好的實現了對分佈式事務的支持。


分享到:


相關文章: