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

也就是說,Seata會根據(jù)不同的業(yè)務SQL來生成beforeImage,有點經(jīng)驗的小伙伴能夠看出,這里其實使用到了模版模式加上策略模式,我們挑一個DeleteExecutor來看一下:
@Override
protected TableRecords beforeImage() throws SQLException {
SQLDeleteRecognizer visitor = (SQLDeleteRecognizer) sqlRecognizer;
// 根據(jù)表名解析出元數(shù)據(jù)
TableMeta tmeta = getTableMeta(visitor.getTableName());
ArrayList<List<Object>> paramAppenderList = new ArrayList<>();
// 生成查詢beforeImage的SQL語句
// SELECT [列名,] FROM [表名] (別名) (WHERE) (ORDER BY) (LIMIT) FOR UPDATE
String selectSQL = buildBeforeImageSQL(visitor, tmeta, paramAppenderList);
// 執(zhí)行SQL查詢beforeImage
return buildTableRecords(tmeta, selectSQL, paramAppenderList);
}
1.關鍵原理就是根據(jù)業(yè)務SQL反向查詢出被影響的數(shù)據(jù);
2.為了保證查詢到的數(shù)據(jù)不是快照數(shù)據(jù),一定要記得加上FOR UPDATE;
另外的話,我們發(fā)現(xiàn)其實UpdateExecutor的前鏡像生成方式和DeleteExecutor也差不多,像普通的insert這種SQL的前鏡像就更簡單了:
@Override
protected TableRecords beforeImage() throws SQLException {
return TableRecords.empty(getTableMeta());
}
因為普通insert語句不存在任何前鏡像,所以直接返回空記錄;
后鏡像
我們再來看一下后鏡像是如何生成的,這次我們看一下UpdateExecutor的后鏡像生成方法afterImage():
@Override
protected TableRecords afterImage(TableRecords beforeImage) throws SQLException {
// 獲取元數(shù)據(jù)
TableMeta tmeta = getTableMeta();
// 沒有前鏡像,也不存在后鏡像,說明沒有數(shù)據(jù)被修改
if (beforeImage == null || beforeImage.size() == 0) {
return TableRecords.empty(getTableMeta());
}
// 生成查詢后鏡像SQL
// SELECT [列名,] FROM [表名] (別名) WHERE 主鍵 in (前鏡像的主鍵值)
// 這里面有一個配置項[client.undo.onlyCareUpdateColumns],是否只關心被修改的列名,默認是true
String selectSQL = buildAfterImageSQL(tmeta, beforeImage);
ResultSet rs = null;
try (PreparedStatement pst = statementProxy.getConnection().prepareStatement(selectSQL)) {
SqlGenerateUtils.setParamForPk(beforeImage.pkRows(), getTableMeta().getPrimaryKeyOnlyName(), pst);
// 執(zhí)行查詢后鏡像
rs = pst.executeQuery();
// 包裝查詢結果
return TableRecords.buildRecords(tmeta, rs);
} finally {
IOUtil.close(rs);
}
}
后鏡像的生成原理與前鏡像的生成原理差不多,不過還是有一些小小的區(qū)別的:
1.后鏡像的查詢條件使用的是前鏡像對應的主鍵值,就沒有用業(yè)務SQL的查詢條件;不同的Executor處理方式不同,需要根據(jù)具體的業(yè)務SQL來區(qū)分;
2.查詢后鏡像的SQL沒有使用FOR UPDATE加鎖,直接拿的快照數(shù)據(jù);
小結
通過對seata源碼的分析,我們現(xiàn)在已經(jīng)了解了前后鏡像的生成原理了:
1.通過業(yè)務SQL來判斷SQL語句的類型,從而選擇不同的Executor來獲取前后鏡像;
2.前鏡像是通過業(yè)務SQL的查詢條件,并加上FOR UPDATE來查詢業(yè)務SQL執(zhí)行前的數(shù)據(jù);(不同的Executor實現(xiàn)不同)
3.后鏡像是在業(yè)務SQL執(zhí)行完畢后,根據(jù)前鏡像內的主鍵數(shù)據(jù)來獲取的數(shù)據(jù);(不同的Executor實現(xiàn)不同)
4.通過前后鏡像的多種實現(xiàn)可以判斷出seata AT模式所支持的SQL語句的所有類型;
以上就是Seata AT模式前后鏡像是如何生成詳解的詳細內容,更多關于Seata AT模式生成前后鏡像的資料請關注腳本之家其它相關文章!
相關文章
SpringBoot中的異常處理與參數(shù)校驗的方法實現(xiàn)
這篇文章主要介紹了SpringBoot中的異常處理與參數(shù)校驗的方法實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2020-04-04
MyBatis深入解讀動態(tài)SQL的實現(xiàn)
動態(tài) SQL 是 MyBatis 的強大特性之一。如果你使用過 JDBC 或其它類似的框架,你應該能理解根據(jù)不同條件拼接 SQL 語句有多痛苦,例如拼接時要確保不能忘記添加必要的空格,還要注意去掉列表最后一個列名的逗號。利用動態(tài) SQL,可以徹底擺脫這種痛苦2022-04-04
Java并發(fā)編程 interrupt()方法示例詳解
interrrupt()方法可以用來打斷正在運行的線程,也可以打斷sleep()、wait()、join()情況下的線程,但是這些情況下被打斷線程的打斷標記不同,這篇文章主要介紹了Java并發(fā)編程 interrupt()方法示例詳解,需要的朋友可以參考下2023-06-06

