Seata?AT模式前后鏡像是如何生成詳解
前言
在Seata官網中,我們可以知道AT模式一階段的處理流程如下:
1.解析 SQL:得到 SQL 的類型(UPDATE),表(product),條件(where name = 'TXC')等相關的信息。
2.查詢前鏡像:根據解析得到的條件信息,生成查詢語句,定位數(shù)據。
3.執(zhí)行業(yè)務 SQL。
4.查詢后鏡像:根據前鏡像的結果,通過 主鍵 定位數(shù)據。
5.插入回滾日志:把前后鏡像數(shù)據以及業(yè)務 SQL 相關的信息組成一條回滾日志記錄,插入到 UNDO_LOG 表中
......
前鏡像的作用是保證在分布式事務失敗時能夠成功回滾的重要依據,后鏡像是在回滾前校驗是否臟寫的數(shù)據依據,那么我們一階段的前后鏡像在真實的代碼實現(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的,根據源碼來看,我們發(fā)現(xiàn)beforeImage()方法有很多實現(xiàn):

也就是說,Seata會根據不同的業(yè)務SQL來生成beforeImage,有點經驗的小伙伴能夠看出,這里其實使用到了模版模式加上策略模式,我們挑一個DeleteExecutor來看一下:
@Override
protected TableRecords beforeImage() throws SQLException {
SQLDeleteRecognizer visitor = (SQLDeleteRecognizer) sqlRecognizer;
// 根據表名解析出元數(shù)據
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.關鍵原理就是根據業(yè)務SQL反向查詢出被影響的數(shù)據;
2.為了保證查詢到的數(shù)據不是快照數(shù)據,一定要記得加上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ù)據
TableMeta tmeta = getTableMeta();
// 沒有前鏡像,也不存在后鏡像,說明沒有數(shù)據被修改
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處理方式不同,需要根據具體的業(yè)務SQL來區(qū)分;
2.查詢后鏡像的SQL沒有使用FOR UPDATE加鎖,直接拿的快照數(shù)據;
小結
通過對seata源碼的分析,我們現(xiàn)在已經了解了前后鏡像的生成原理了:
1.通過業(yè)務SQL來判斷SQL語句的類型,從而選擇不同的Executor來獲取前后鏡像;
2.前鏡像是通過業(yè)務SQL的查詢條件,并加上FOR UPDATE來查詢業(yè)務SQL執(zhí)行前的數(shù)據;(不同的Executor實現(xiàn)不同)
3.后鏡像是在業(yè)務SQL執(zhí)行完畢后,根據前鏡像內的主鍵數(shù)據來獲取的數(shù)據;(不同的Executor實現(xiàn)不同)
4.通過前后鏡像的多種實現(xiàn)可以判斷出seata AT模式所支持的SQL語句的所有類型;
以上就是Seata AT模式前后鏡像是如何生成詳解的詳細內容,更多關于Seata AT模式生成前后鏡像的資料請關注腳本之家其它相關文章!
相關文章
詳解Java的Hibernat框架中的Map映射與SortedMap映射
這篇文章主要介紹了Java的Hibernat框架中的Map映射與SortedMap映射,Hibernat是Java的SSH三大web開發(fā)框架之一,需要的朋友可以參考下2015-12-12
Java中Future、FutureTask原理以及與線程池的搭配使用
這篇文章主要為大家詳細介紹了Java中Future、FutureTask原理以及與線程池的搭配使用,具有一定的參考價值,感興趣的小伙伴們可以參考一下2019-09-09
解決rror updating database.Cause:java.sql.SQLSyntaxE
這篇文章主要介紹了解決rror updating database.Cause:java.sql.SQLSyntaxErrorException問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-05-05

