Mysql分布式事務(wù)
為了規(guī)范分布式事務(wù)的管理,X/OPEN 提出了分布式事務(wù)處理規(guī)范XA協(xié)議,XA規(guī)范了TM與RM之間的通信接口,在TM與多個(gè)RM之間形成一個(gè)雙向通信橋梁,從而在多個(gè)數(shù)據(jù)庫(kù)資源下保證ACID四個(gè)特性。目前知名的數(shù)據(jù)庫(kù),如Oracle, DB2,mysql等,都是實(shí)現(xiàn)了XA接口的,都可以作為RM。
XA是數(shù)據(jù)庫(kù)的分布式事務(wù),強(qiáng)一致性,在整個(gè)過程中,數(shù)據(jù)都處于被鎖住的狀態(tài),即從prepare到commit、rollback的整個(gè)過程中,TM一直擁有參與分布式事務(wù)RM對(duì)應(yīng)的數(shù)據(jù)庫(kù)的鎖,如果有其他人要修改數(shù)據(jù)庫(kù)的該條數(shù)據(jù),就必須等待鎖的釋放,存在長(zhǎng)事務(wù)風(fēng)險(xiǎn)。
分布式事務(wù)模型
X/Open定義了分布式事務(wù)處理模型,包括應(yīng)用程序AP、事務(wù)管理器TM、資源管理器RM、通信資源管理器CRM。
在XA規(guī)范中分布式事務(wù)有AP、RM、TM組成:
- 應(yīng)用程序(Application Program):定義事務(wù)邊界(定義事務(wù)開始和結(jié)束)并訪問事務(wù)邊界內(nèi)的資源
- 資源管理器(Resource Manager):RM管理計(jì)算機(jī)共享的資源,資源包含比如數(shù)據(jù)庫(kù)、文件系統(tǒng)等
- 事務(wù)管理器(Transaction Manager,簡(jiǎn)稱TM):負(fù)責(zé)管理全局事務(wù),分配事務(wù)唯一標(biāo)識(shí),監(jiān)控事務(wù)的執(zhí)行進(jìn)度,并負(fù)責(zé)事務(wù)的提交、回滾、失敗恢復(fù)等。
分布式事務(wù)的基本流程如下:
具體流程如下:
兩階段提交
當(dāng)每個(gè)RM都結(jié)束了全局事務(wù)的執(zhí)行后,此時(shí)每個(gè)RM管理的分布式事務(wù)分支還沒有提交,只是把該事務(wù)管理的業(yè)務(wù)邏輯執(zhí)行完了。
進(jìn)入二階段提交階段,在這個(gè)階段,會(huì)先進(jìn)入prepare階段,然后再是commit或者rollback階段。
具體流程如圖:
第一階段分為兩個(gè)步驟:
- 事務(wù)管理器通知參與該事務(wù)的各個(gè)資源管理器,通知他們開啟事務(wù)、執(zhí)行SQL(暫不提交),并進(jìn)入prepare狀態(tài)(該狀態(tài)下可執(zhí)行commit/ rollback)。
- 資源管理器接收到消息后開始準(zhǔn)備階段,寫好事務(wù)日志并執(zhí)行事務(wù),但不提交,然后將是否就緒的消息返回給事務(wù)管理器
- RM根據(jù)自己的情況,如果判斷自己進(jìn)行的工作可以被提交,那就就對(duì)工作內(nèi)容進(jìn)行持久化,并給TM回執(zhí)OK;否者給TM的回執(zhí)NO
- RM在發(fā)送了否定答復(fù)并回滾了已經(jīng)的工作后,就可以丟棄這個(gè)事務(wù)分支信息了
第二階段也分為兩個(gè)步驟:
- 事務(wù)管理器在接受各個(gè)消息后,開始分析,如果有任意其一失敗,則發(fā)送回滾命令,否則發(fā)送提交命令。
- 各個(gè)資源管理器接收到命令后,執(zhí)行(耗時(shí)很少),并將提交消息返回給事務(wù)管理器。
兩階段提交的好處是有了事務(wù)管理器進(jìn)行統(tǒng)一管理,讓事務(wù)在提交前盡可能的完成所有能完成的工作。同時(shí)兩階段提交可以保證事務(wù)的一致性,不管是事務(wù)管理器還是各個(gè)資源管理器,每執(zhí)行一步操作都會(huì)被日志記錄,為出現(xiàn)故障后的恢復(fù)提供依據(jù)。
Mysql中的XA語(yǔ)法
Mysql中分布式操作的基本模板如下:
開啟xa事務(wù),XA start DML語(yǔ)句,即SQL增刪改查語(yǔ)句終止XA事務(wù),XA end 預(yù)提交事務(wù), XA prepare ,這一步是有返回值的提交,XA commit ,根據(jù)prepare操作的返回結(jié)果做的處理回滾,XA rollback ,根據(jù)prepare操作的返回結(jié)果做的處理XA RECOVER: 返回當(dāng)前數(shù)據(jù)庫(kù)中處于PREPARE狀態(tài)的分支事務(wù)的詳細(xì)信息
每個(gè)事務(wù)必須有一個(gè)唯一的xid值,因此當(dāng)前值不能被其他XA事務(wù)使用,xid是一個(gè)XA事務(wù)標(biāo)識(shí)符,用來唯一標(biāo)識(shí)一個(gè)分布式事務(wù)。
xid值可以由客戶端提供,或者由Mysql服務(wù)器生成。
xid基本格式如下:
xid: gtrid [,bqual] [, formatID]
- gtrid 是一個(gè)分布式事務(wù)標(biāo)識(shí)符,相同的分布式事務(wù)使用相同的gtrid,這樣可以明確知道XA事務(wù)屬于哪一個(gè)分布式事務(wù)
- bqual是一個(gè)分支限定符,默認(rèn)為空串,對(duì)于一個(gè)分布式事務(wù)中的每個(gè)分支事務(wù),bqual必須是唯一的
- formatID是一個(gè)數(shù)字,用于標(biāo)識(shí)由gtrid和bqual值使用的格式,默認(rèn)為1
XA語(yǔ)法中用到的xid值都必須和START操作使用的xid值相同
使用演示
上面給出的是命令行方式的演示過程,下面再給出java代碼的使用過程:
public MysqlXADataSource xaDataSource(String url,String uname,String pwd){ MysqlXADataSource xa = new MysqlXADataSource(); xa.setUrl(url); xa.setUser(uname); xa.setPassword(pwd); return xa; } @Test public void testXA(){ MysqlXADataSource testXA = xaDataSource(“jdbc:mysql://xxx:3306/test”, “root”, “xxx”); MysqlXADataSource trainingXA = xaDataSource(“jdbc:mysql://xxx:3306/training”, “root”, “xxx”); try { //獲取每個(gè)分支事務(wù)的連接 XAConnection test = testXA.getXAConnection(); XAResource testXAResource = test.getXAResource(); Connection testConn= test.getConnection(); Statement testStatement = testConn.createStatement(); XAConnection train = trainingXA.getXAConnection(); XAResource trainXAResource = train.getXAResource(); Connection trainConn = train.getConnection(); Statement trainStatement = trainConn.createStatement(); //生成每個(gè)分支事務(wù)對(duì)應(yīng)的XID MysqlXid testXid = new MysqlXid(“test”.getBytes(StandardCharsets.UTF_8), “testDB”.getBytes(StandardCharsets.UTF_8), 1); MysqlXid trainXid = new MysqlXid(“test”.getBytes(StandardCharsets.UTF_8), “trainDB”.getBytes(StandardCharsets.UTF_8), 1); //事務(wù)分支1關(guān)聯(lián)分支事務(wù)sql語(yǔ)句 testXAResource.start(testXid,XAResource.TMNOFLAGS); testStatement.execute(“UPDATE stu SET classId=3 WHERE id=1”); testXAResource.end(testXid,XAResource.TMSUCCESS); //事務(wù)分支2關(guān)聯(lián)分支事務(wù)sql語(yǔ)句 trainXAResource.start(trainXid,XAResource.TMNOFLAGS); trainStatement.execute(“UPDATE Salers SET SNO=”123″ WHERE SNAME=”123″”); trainXAResource.end(trainXid,XAResource.TMSUCCESS); //兩階段提交協(xié)議第一階段 int testRes = testXAResource.prepare(testXid); int trainRes = trainXAResource.prepare(trainXid); //兩階段提交協(xié)議第二階段 if(XAResource.XA_OK==testRes && XAResource.XA_OK==trainRes){ //存儲(chǔ)引擎級(jí)別事務(wù)提交 testXAResource.commit(testXid,false); trainXAResource.commit(trainXid,false); }else { testXAResource.rollback(testXid); trainXAResource.rollback(trainXid); } } catch (SQLException | XAException e) { e.printStackTrace(); } }
上面代碼中出現(xiàn)了一些XAResource標(biāo)記,這里解釋一下:
- XAResource#start方法開啟一個(gè)分支事務(wù)
下面提到的名詞都是XAResource類中的常量,分別對(duì)應(yīng)一個(gè)整數(shù)值
XAResource:void start(Xid xid, int flags) throws XAException;
如果這里標(biāo)記傳入的是 TMJOIN ,會(huì)嘗試去加入上一個(gè)被RM記錄的事務(wù)中去。如果 TMRESUME 被設(shè)置了,會(huì)嘗試去恢復(fù)xid與自己相同的并且是被掛起的事務(wù)分支。
如果沒有設(shè)置上面兩個(gè)標(biāo)記,并且還找到了一個(gè)分支事務(wù)并且該分支事務(wù)xid與自己相同,那么會(huì)拋出異常
一般無(wú)特殊情況推薦使用TMNOFLAGS,表示不設(shè)置任何標(biāo)志
- XAResource#end方法結(jié)束當(dāng)前分支事務(wù)的執(zhí)行
XAResource:void end(Xid xid, int flags) throws XAException;
這里標(biāo)記的設(shè)置分為了三種情況:
- TMSUCCESS: 該分支事務(wù)已經(jīng)成功完成了
- TMFAIL: 該分支事務(wù)執(zhí)行失敗,RM會(huì)標(biāo)記當(dāng)前事務(wù)為回滾狀態(tài)
- TMSUSPEND: 會(huì)將當(dāng)前分支事務(wù)臨時(shí)掛起進(jìn)入未完成狀態(tài),當(dāng)前事務(wù)被掛起后需要通過start方法設(shè)置TMRESUME來恢復(fù)
- XAResource#commit方法提交當(dāng)前分支事務(wù)
XAResource:void commit(Xid xid, boolean onePhase) throws XAException;
onePhase標(biāo)志是否為一階段提交,兩階段提交協(xié)議中,如果只有一個(gè)RM參與,那么可以優(yōu)化為一階段提交。
相當(dāng)于跳過了prepare一階段提交,變成了局部事務(wù)的處理方式
XA狀態(tài)轉(zhuǎn)換圖
XA的BUG
在Mysql 5.5之前的版本中,如果分支事務(wù)到達(dá)prepare狀態(tài),此時(shí)數(shù)據(jù)庫(kù)異常重啟后,可以選擇對(duì)分支事務(wù)進(jìn)行提交或者回滾,但是即使選擇提交事務(wù),該事務(wù)也不會(huì)被寫入BINLOG日志,這會(huì)導(dǎo)致在使用BINLOG恢復(fù)數(shù)據(jù)時(shí),丟失部分?jǐn)?shù)據(jù),并在如果存在從庫(kù),可能導(dǎo)致主從數(shù)據(jù)庫(kù)的數(shù)據(jù)不一致。
此外,如果是分支事務(wù)的客戶端連接異常終止的話,例如執(zhí)行prepare之后退出連接,那么數(shù)據(jù)庫(kù)會(huì)自動(dòng)回滾未完成的事務(wù),之所以這樣做是因?yàn)閷?duì)于prepare的事務(wù),MySQL 是不會(huì)記錄binlog的(官方說是減少fsync, 起到了優(yōu)化的作用)。只有當(dāng)分布式事務(wù)提交的時(shí)候才會(huì)把前面的操作寫入binlog信息,所以對(duì)于binlog來說,分布式事務(wù)與普通的事務(wù)沒有區(qū)別,而prepare以
前的操作信息都保存在連接的I0 CACHE中,如果這個(gè)時(shí)候客戶端退出了,以前的binlog信息都會(huì)被丟失,再次重連后允許提交的話,會(huì)造成Binlog丟失,從而造成主從數(shù)據(jù)的不一致,所以官方在客戶端退出的時(shí)候直接把已經(jīng)prepare的事務(wù)都回滾了!
但是如果分布式事務(wù)情況下,其他分支事務(wù)都成功提交,這個(gè)分支回滾,會(huì)導(dǎo)致分布式事務(wù)的不完整,丟失部分分支事務(wù)內(nèi)容。
MySql 5.7中做了以下優(yōu)化: 在session斷開和實(shí)例崩潰的情況下,事務(wù)都不會(huì)自動(dòng)回滾,同時(shí)在XA PREPARE時(shí),之前的事務(wù)信息就會(huì)被寫入到BINLOG并同步到從庫(kù),最終再由用戶決定事務(wù)回滾或者提交。
XA的性能問題
- XA事務(wù)和本地事務(wù)以及鎖表操作是互斥的,因?yàn)閄A事務(wù)會(huì)鎖住當(dāng)前表
- 開啟了xa事務(wù)就無(wú)法使用本地事務(wù)和鎖表操作
- 開啟了本地事務(wù)就無(wú)法使用xa事務(wù)
1)在執(zhí)行分支事務(wù)時(shí),會(huì)將RM資源鎖住,需要等到所有的RM響應(yīng),等到第二階段執(zhí)行完畢時(shí)(提交/回滾),RM的鎖才會(huì)釋放,在高并發(fā)場(chǎng)所不適用。
2)XA方案依賴于本地?cái)?shù)據(jù)庫(kù)對(duì)XA協(xié)議的支持,如果本地?cái)?shù)據(jù)庫(kù)不支持XA協(xié)議那么第三方程序(Java)將操作不了。例如許多非關(guān)系型數(shù)據(jù)庫(kù)并沒有支持XA。
3)MySQL對(duì)XA方案支持的不太友好,MySQL的XA實(shí)現(xiàn),沒有記錄prepare階段日志。
原文鏈接:blog.csdn.net/m0_53157173/article/details/125275556?utm_source=tuicool&utm_medium=referral