4.11.3 什么是XA 事務?
《數據庫程序員面試筆試寶典》第4章數據庫基礎,本章主要介紹數據庫基礎部分的面試題,比較適合應屆畢業生,也適合由其他崗位轉數據庫崗位的人員。本節為大家介紹什么是XA 事務。
- 作者:猿媛之家
- 來源:機械工業出版社| 2018-11-09
4.11.3 什么是XA 事務?
XA(eXtended Architecture)是指由X/Open 組織提出的分布式交易處理的規范。XA 是一個分布式事務協議,由Tuxedo 提出,所以分布式事務也稱為XA 事務。XA 協議主要定義了事務管理器TM(Transaction Manager,協調者)和資源管理器RM(Resource Manager,參與者)之間的接口。其中,資源管理器往往由數據庫實現,如Oracle、DB2、MySQL,這些商業數據庫都實現了XA 接口,而事務管理器作為全局的調度者,負責各個本地資源的提交和回滾。XA 事務是基於兩階段提交(Two-phaseCommit,2PC)協議實現的,可以保證數據的強一致性,許多分布式關系型數據管理系統都采用此協議來完成分布式。階段一為准備階段,即所有的參與者准備執行事務並鎖住需要的資源。當參與者Ready時,向TM 匯報自己已經准備好。階段二為提交階段。當TM 確認所有參與者都Ready 后,向所有參與者發送COMMIT 命令。
XA 事務允許不同數據庫的分布式事務,只要參與在全局事務中的每個結點都支持XA 事務。Oracle、MySQL 和SQL Server 都支持XA 事務。
XA 事務由一個或多個資源管理器(RM)、一個事務管理器(TM)和一個應用程序(ApplicationProgram)組成。
資源管理器:提供訪問事務資源的方法。通常一個數據庫就是一個資源管理器。
事務管理器:協調參與全局事務中的各個事務。需要和參與全局事務的所有資源管理器進行通信。
應用程序:定義事務的邊界。
XA 事務的缺點是性能不好,且無法滿足高並發場景。一個數據庫的事務和多個數據庫間的XA 事務性能會相差很多。因此,要盡量避免XA 事務,如可以將數據寫入本地,用高性能的消息系統分發數據,或使用數據庫復制等技術。只有在其他辦法都無法實現業務需求,且性能不是瓶頸時才使用XA。
X/Open DTP模型(1994)包括應用程序(AP)、事務管理器(TM)、資源管理器(RM)、通信資源管理器(CRM)四部分。
在這個模型中,通常事務管理器(TM)是交易中間件,資源管理器(RM)是數據庫,通信資源管理器(CRM)是消息中間件。
一般情況下,某一數據庫無法知道其它數據庫在做什么,因此,在一個DTP環境中,交易中間件是必需的,由它通知和協調相關數據庫的提交或回滾。而一個數據庫只將其自己所做的操作(可恢復)影射到全局事務中。
XA就是X/Open DTP定義的交易中間件與數據庫之間的接口規范(即接口函數),交易中間件用它來通知數據庫事務的開始、結束以及提交、回滾等
https://www.jianshu.com/p/c2d8f2bbcb6f
MySQL分布式事務操作
1. MySQL XA事務的語法
主要有:
# 在mysql實例中開啟一個XA事務,指定一個全局唯一標識;
mysql> XA START 'any_unique_id';
# XA事務的操作結束;
mysql> XA END 'any_unique_id ';
# 告知mysql准備提交這個xa事務;
mysql> XA PREPARE 'any_unique_id';
# 告知mysql提交這個xa事務;
mysql> XA COMMIT 'any_unique_id';
# 告知mysql回滾這個xa事務;
mysql> XA ROLLBACK 'any_unique_id';
# 查看本機mysql目前有哪些xa事務處於prepare狀態;
mysql> XA RECOVER;
2. XA事務恢復
如果執行分布式事務的mysql crash了,MySQL按照如下邏輯進行恢復:
a. 如果這個xa事務commit了,那么什么也不用做。
b. 如果這個xa事務還沒有prepare,那么直接回滾它。
c. 如果這個xa事務prepare了,還沒commit, 那么把它恢復到prepare的狀態,由用戶去決定commit或rollback。
當mysql crash后重新啟動之后,執行“XA RECOVER;”查看當前處於prepare狀態的xa事務,然后commit或rollback它們。
MySQL分布式事務限制
a. XA事務和本地事務以及鎖表操作是互斥的
開啟了xa事務就無法使用本地事務和鎖表操作
mysql> xa start 't1xa';
Query OK, 0 rows affected (0.04 sec)
mysql> begin;
ERROR 1399 (XAE07): XAER_RMFAIL: The command cannot be executed when global transaction is in theACTIVE state
mysql> lock table t read;
ERROR 1399 (XAE07): XAER_RMFAIL: The command cannot be executed when global transaction is in theACTIVE state
開啟了本地事務就無法使用xa事務
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> xa start 'rrrr';
ERROR 1400 (XAE09): XAER_OUTSIDE: Some work is done outside global transaction
b. xa start之后必須xa end,否則不能執行xa commit和xa rollback
所以如果在執行xa事務過程中有語句出錯了,你也需要先xa end一下,然后才能xa rollback。
mysql> xa start 'tt';
Query OK, 0 rows affected (0.00 sec)
mysql> xa rollback 'tt';
ERROR 1399 (XAE07): XAER_RMFAIL: The command cannot be executed when global transaction is in the ACTIVE state
mysql> xa end 'tt';
Query OK, 0 rows affected (0.00 sec)
mysql> xa rollback 'tt';
Query OK, 0 rows affected (0.00 sec)
MySQL 5.7對分布式事務的支持
一直以來,MySQL數據庫是支持分布式事務的,但是只能說是有限的支持,具體表現在:
已經prepare的事務,在客戶端退出或者服務宕機的時候,2PC的事務會被回滾。
在服務器故障重啟提交后,相應的Binlog被丟失。
上述問題存在於MySQL數據庫長達數十年的時間,直到MySQL-5.7.7版本,官方才修復了該問題。下面將會詳細介紹下該問題的具體表現和官方修復方法,這里分別采用官方MySQL-5.6.27版本(未修復)和MySQL-5.7.9版本(已修復)進行驗證。
先來看下存在的問題,我們先創建一個表如下:
CREATE TABLE t(
id INT AUTO_INCREMENT PRIMARY KEY,
a INT
)ENGINE=InnoDB;
對於上述表,通過如下操作進行數據插入:
mysql> XA START 'mysql56';
mysql> INSERT INTO t VALUES(1,1);
mysql> XA END 'mysql56';
mysql> XA PREPARE 'mysql56';
通過上面的操作,用戶創建了一個分布式事務,並且prepare沒有返回錯誤,說明該分布式事務可以被提交。通過命令XA RECOVER查看顯示如下結果:
mysql> XA RECOVER;
+----------+--------------+--------------+---------+
| formatID | gtrid_length | bqual_length | data |
+----------+--------------+--------------+---------+
| 1 | 7 | 0 | mysql56 |
+----------+--------------+--------------+---------+
若這時候用戶退出客戶端后重連,通過命令xa recover會發現剛才創建的2PC事務不見了。即prepare成功的事務丟失了,不符合2PC協議規范!!!
產生上述問題的主要原因在於:MySQL 5.6版本在客戶端退出的時候,自動把已經prepare的事務回滾了,那么MySQL為什么要這樣做?這主要取決於MySQL的內部實現,MySQL 5.7以前的版本,對於prepare的事務,MySQL是不會記錄binlog的(官方說是減少fsync,起到了優化的作用)。只有當分布式事務提交的時候才會把前面的操作寫入binlog信息,所以對於binlog來說,分布式事務與普通的事務沒有區別,而prepare以前的操作信息都保存在連接的IO_CACHE中,如果這個時候客戶端退出了,以前的binlog信息都會被丟失,再次重連后允許提交的話,會造成Binlog丟失,從而造成主從數據的不一致,所以官方在客戶端退出的時候直接把已經prepare的事務都回滾了!
官方的做法,貌似干得很漂亮,犧牲了一點標准化的東西,至少保證了主從數據的一致性。但其實不然,若用戶已經prepare后在客戶端退出之前,MySQL發生了宕機,這個時候又會怎樣?
MySQL在某個分布式事務prepare成功后宕機,宕機前操作該事務的連接並沒有斷開,這個時候已經prepare的事務並不會被回滾,所以在MySQL重新啟動后,引擎層通過recover機制能恢復該事務。當然該事務的Binlog已經在宕機過程中被丟失,這個時候,如果去提交,則會造成主從數據的不一致,即提交沒有記錄Binlog,從上丟失該條數據。所以對於這種情況,官方一般建議直接回滾已經prepare的事務。
以上是MySQL 5.7以前版本MySQL在分布式事務上的各種問題,那么MySQL 5.7版本官方做了哪些改進?這個可以從官方的WL#6860描述上得到一些信息,我們還是本着沒有實踐就沒有發言權的態度,從具體的操作上來分析下MySQL 5.7的改進方法。還是以上面同樣的表結構進行同樣的操作如下:
mysql> XA START 'mysql57';
mysql> INSERT INTO t VALUES(1,1);
mysql> XA END 'mysql57';
mysql> XA PREPARE 'mysql57'
這個時候,我們通過mysqlbinlog來查看下Master上的Binlog,結果如下:

同時也對比下Slave上的Relay log,如下:

通過上面的操作,明顯發現在prepare以后,從XA START到XA PREPARE之間的操作都被記錄到了Master的Binlog中,然后通過復制關系傳到了Slave上。也就是說MySQL 5.7開始,MySQL對於分布式事務,在prepare的時候就完成了寫Binlog的操作,通過新增一種叫XA_prepare_log_event的event類型來實現,這是與以前版本的主要區別(以前版本prepare時不寫Binlog)。
當然僅靠這一點是不夠的,因為我們知道Slave通過SQL thread來回放Relay log信息,由於prepare的事務能阻塞整個session,而回放的SQL thread只有一個(不考慮並行回放),那么SQL thread會不會因為被分布式事務的prepare階段所阻塞,從而造成整個SQL thread回放出現問題?這也正是官方要解決的第二個問題:怎么樣能使SQL thread在回放到分布式事務的prepare階段時,不阻塞后面event的回放?其實這個實現也很簡單(在xa.cc::applier_reset_xa_trans),只要在SQL thread回放到prepare的時候,進行類似於客戶端斷開連接的處理即可(把相關cache與SQL thread的連接句柄脫離)。最后在Slave服務器上,用戶通過命令XA RECOVER可以查到如下信息:
mysql> XA RECOVER;
+----------+--------------+--------------+---------+
| formatID | gtrid_length | bqual_length | data |
+----------+--------------+--------------+---------+
| 1 | 7 | 0 | mysql57 |
+----------+--------------+--------------+---------+
至於上面的事務什么時候提交,一般等到Master上進行XA COMMIT ‘mysql57’后,slave上也同時會被提交。
總結
綜上所述,MySQL 5.7對於分布式事務的支持變得完美了,一個長達數十年的bug又被修復了,因而又多了一個升級到MySQL 5.7版本的理由。