Seata?AT模式前后鏡像是如何生成詳解
前言
在Seata官網(wǎng)中,我們可以知道AT模式一階段的處理流程如下:
1.解析 SQL:得到 SQL 的類(lèi)型(UPDATE),表(product),條件(where name = 'TXC')等相關(guān)的信息。
2.查詢(xún)前鏡像:根據(jù)解析得到的條件信息,生成查詢(xún)語(yǔ)句,定位數(shù)據(jù)。
3.執(zhí)行業(yè)務(wù) SQL。
4.查詢(xún)后鏡像:根據(jù)前鏡像的結(jié)果,通過(guò) 主鍵 定位數(shù)據(jù)。
5.插入回滾日志:把前后鏡像數(shù)據(jù)以及業(yè)務(wù) SQL 相關(guān)的信息組成一條回滾日志記錄,插入到 UNDO_LOG 表中
......
前鏡像的作用是保證在分布式事務(wù)失敗時(shí)能夠成功回滾的重要依據(jù),后鏡像是在回滾前校驗(yàn)是否臟寫(xiě)的數(shù)據(jù)依據(jù),那么我們一階段的前后鏡像在真實(shí)的代碼實(shí)現(xiàn)中是如何生成的呢?
前后鏡像的生成
為了能夠探尋到前后鏡像的生成原理,我們需要看一看seata源碼。最終我們把入口定位到AbstractDMLBaseExecutor.executeAutoCommitFalse()方法:
protected T executeAutoCommitFalse(Object[] args) throws Exception {
// 獲取前鏡像
TableRecords beforeImage = beforeImage();
// 執(zhí)行業(yè)務(wù)SQL
T result = statementCallback.execute(statementProxy.getTargetStatement(), args);
// 獲取后鏡像
TableRecords afterImage = afterImage(beforeImage);
// 存儲(chǔ)前后鏡像
prepareUndoLog(beforeImage, afterImage);
return result;
}
前鏡像
繼續(xù)深入探究一下到底是怎么獲取beforeImage的,根據(jù)源碼來(lái)看,我們發(fā)現(xiàn)beforeImage()方法有很多實(shí)現(xiàn):

也就是說(shuō),Seata會(huì)根據(jù)不同的業(yè)務(wù)SQL來(lái)生成beforeImage,有點(diǎn)經(jīng)驗(yàn)的小伙伴能夠看出,這里其實(shí)使用到了模版模式加上策略模式,我們挑一個(gè)DeleteExecutor來(lái)看一下:
@Override
protected TableRecords beforeImage() throws SQLException {
SQLDeleteRecognizer visitor = (SQLDeleteRecognizer) sqlRecognizer;
// 根據(jù)表名解析出元數(shù)據(jù)
TableMeta tmeta = getTableMeta(visitor.getTableName());
ArrayList<List<Object>> paramAppenderList = new ArrayList<>();
// 生成查詢(xún)beforeImage的SQL語(yǔ)句
// SELECT [列名,] FROM [表名] (別名) (WHERE) (ORDER BY) (LIMIT) FOR UPDATE
String selectSQL = buildBeforeImageSQL(visitor, tmeta, paramAppenderList);
// 執(zhí)行SQL查詢(xún)beforeImage
return buildTableRecords(tmeta, selectSQL, paramAppenderList);
}
1.關(guān)鍵原理就是根據(jù)業(yè)務(wù)SQL反向查詢(xún)出被影響的數(shù)據(jù);
2.為了保證查詢(xún)到的數(shù)據(jù)不是快照數(shù)據(jù),一定要記得加上FOR UPDATE;
另外的話,我們發(fā)現(xiàn)其實(shí)UpdateExecutor的前鏡像生成方式和DeleteExecutor也差不多,像普通的insert這種SQL的前鏡像就更簡(jiǎn)單了:
@Override
protected TableRecords beforeImage() throws SQLException {
return TableRecords.empty(getTableMeta());
}
因?yàn)槠胀?code>insert語(yǔ)句不存在任何前鏡像,所以直接返回空記錄;
后鏡像
我們?cè)賮?lái)看一下后鏡像是如何生成的,這次我們看一下UpdateExecutor的后鏡像生成方法afterImage():
@Override
protected TableRecords afterImage(TableRecords beforeImage) throws SQLException {
// 獲取元數(shù)據(jù)
TableMeta tmeta = getTableMeta();
// 沒(méi)有前鏡像,也不存在后鏡像,說(shuō)明沒(méi)有數(shù)據(jù)被修改
if (beforeImage == null || beforeImage.size() == 0) {
return TableRecords.empty(getTableMeta());
}
// 生成查詢(xún)后鏡像SQL
// SELECT [列名,] FROM [表名] (別名) WHERE 主鍵 in (前鏡像的主鍵值)
// 這里面有一個(gè)配置項(xiàng)[client.undo.onlyCareUpdateColumns],是否只關(guān)心被修改的列名,默認(rèn)是true
String selectSQL = buildAfterImageSQL(tmeta, beforeImage);
ResultSet rs = null;
try (PreparedStatement pst = statementProxy.getConnection().prepareStatement(selectSQL)) {
SqlGenerateUtils.setParamForPk(beforeImage.pkRows(), getTableMeta().getPrimaryKeyOnlyName(), pst);
// 執(zhí)行查詢(xún)后鏡像
rs = pst.executeQuery();
// 包裝查詢(xún)結(jié)果
return TableRecords.buildRecords(tmeta, rs);
} finally {
IOUtil.close(rs);
}
}
后鏡像的生成原理與前鏡像的生成原理差不多,不過(guò)還是有一些小小的區(qū)別的:
1.后鏡像的查詢(xún)條件使用的是前鏡像對(duì)應(yīng)的主鍵值,就沒(méi)有用業(yè)務(wù)SQL的查詢(xún)條件;不同的Executor處理方式不同,需要根據(jù)具體的業(yè)務(wù)SQL來(lái)區(qū)分;
2.查詢(xún)后鏡像的SQL沒(méi)有使用FOR UPDATE加鎖,直接拿的快照數(shù)據(jù);
小結(jié)
通過(guò)對(duì)seata源碼的分析,我們現(xiàn)在已經(jīng)了解了前后鏡像的生成原理了:
1.通過(guò)業(yè)務(wù)SQL來(lái)判斷SQL語(yǔ)句的類(lèi)型,從而選擇不同的Executor來(lái)獲取前后鏡像;
2.前鏡像是通過(guò)業(yè)務(wù)SQL的查詢(xún)條件,并加上FOR UPDATE來(lái)查詢(xún)業(yè)務(wù)SQL執(zhí)行前的數(shù)據(jù);(不同的Executor實(shí)現(xiàn)不同)
3.后鏡像是在業(yè)務(wù)SQL執(zhí)行完畢后,根據(jù)前鏡像內(nèi)的主鍵數(shù)據(jù)來(lái)獲取的數(shù)據(jù);(不同的Executor實(shí)現(xiàn)不同)
4.通過(guò)前后鏡像的多種實(shí)現(xiàn)可以判斷出seata AT模式所支持的SQL語(yǔ)句的所有類(lèi)型;
以上就是Seata AT模式前后鏡像是如何生成詳解的詳細(xì)內(nèi)容,更多關(guān)于Seata AT模式生成前后鏡像的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
SpringBoot中的異常處理與參數(shù)校驗(yàn)的方法實(shí)現(xiàn)
這篇文章主要介紹了SpringBoot中的異常處理與參數(shù)校驗(yàn)的方法實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-04-04
SpringBoot讀取資源目錄中JSON文件的方法實(shí)例
最近做項(xiàng)目遇到需要將json類(lèi)型的配置文件引用到項(xiàng)目中,已經(jīng)將讀取json文件的方法封裝成工具類(lèi),下面這篇文章主要給大家介紹了關(guān)于SpringBoot讀取資源目錄中JSON文件的相關(guān)資料,需要的朋友可以參考下2023-04-04
MyBatis深入解讀動(dòng)態(tài)SQL的實(shí)現(xiàn)
動(dòng)態(tài) SQL 是 MyBatis 的強(qiáng)大特性之一。如果你使用過(guò) JDBC 或其它類(lèi)似的框架,你應(yīng)該能理解根據(jù)不同條件拼接 SQL 語(yǔ)句有多痛苦,例如拼接時(shí)要確保不能忘記添加必要的空格,還要注意去掉列表最后一個(gè)列名的逗號(hào)。利用動(dòng)態(tài) SQL,可以徹底擺脫這種痛苦2022-04-04
Java并發(fā)編程 interrupt()方法示例詳解
interrrupt()方法可以用來(lái)打斷正在運(yùn)行的線程,也可以打斷sleep()、wait()、join()情況下的線程,但是這些情況下被打斷線程的打斷標(biāo)記不同,這篇文章主要介紹了Java并發(fā)編程 interrupt()方法示例詳解,需要的朋友可以參考下2023-06-06
SpringBoot配置Redis自定義過(guò)期時(shí)間操作
這篇文章主要介紹了SpringBoot配置Redis自定義過(guò)期時(shí)間操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-07-07
Netty分布式pipeline管道傳播事件的邏輯總結(jié)分析
這篇文章主要為大家介紹了Netty分布式pipeline管道傳播事件總結(jié)分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-03-03
Java實(shí)現(xiàn)合并兩個(gè)有序序列算法示例
這篇文章主要介紹了Java實(shí)現(xiàn)合并兩個(gè)有序序列算法,簡(jiǎn)單描述了序列合并算法的原理與java合并有序序列的具體操作步驟及相關(guān)實(shí)現(xiàn)技巧,需要的朋友可以參考下2017-09-09

