Seata AT模式前后镜像是如何生成详解

 更新时间:2022年11月29日 11:40:52   作者:梦想实现家_Z  
这篇文章主要为大家介绍了Seata AT模式前后镜像是如何生成的方法详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪

前言

Seata官网中,我们可以知道AT模式一阶段的处理流程如下:

1.解析 SQL:得到 SQL 的类型(UPDATE),表(product),条件(where name = 'TXC')等相关的信息。

2.查询前镜像:根据解析得到的条件信息,生成查询语句,定位数据。

3.执行业务 SQL。

4.查询后镜像:根据前镜像的结果,通过 主键 定位数据。

5.插入回滚日志:把前后镜像数据以及业务 SQL 相关的信息组成一条回滚日志记录,插入到 UNDO_LOG 表中

......

前镜像的作用是保证在分布式事务失败时能够成功回滚的重要依据,后镜像是在回滚前校验是否脏写的数据依据,那么我们一阶段的前后镜像在真实的代码实现中是如何生成的呢?

前后镜像的生成

为了能够探寻到前后镜像的生成原理,我们需要看一看seata源码。最终我们把入口定位到AbstractDMLBaseExecutor.executeAutoCommitFalse()方法:

    protected T executeAutoCommitFalse(Object[] args) throws Exception {
        // 获取前镜像
        TableRecords beforeImage = beforeImage();
        // 执行业务SQL
        T result = statementCallback.execute(statementProxy.getTargetStatement(), args);
        // 获取后镜像
        TableRecords afterImage = afterImage(beforeImage);
        // 存储前后镜像
        prepareUndoLog(beforeImage, afterImage);
        return result;
    }

前镜像

继续深入探究一下到底是怎么获取beforeImage的,根据源码来看,我们发现beforeImage()方法有很多实现:

也就是说,Seata会根据不同的业务SQL来生成beforeImage,有点经验的小伙伴能够看出,这里其实使用到了模版模式加上策略模式,我们挑一个DeleteExecutor来看一下:

@Override
    protected TableRecords beforeImage() throws SQLException {
        SQLDeleteRecognizer visitor = (SQLDeleteRecognizer) sqlRecognizer;
        // 根据表名解析出元数据
        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);
        // 执行SQL查询beforeImage
        return buildTableRecords(tmeta, selectSQL, paramAppenderList);
    }

1.关键原理就是根据业务SQL反向查询出被影响的数据;

2.为了保证查询到的数据不是快照数据,一定要记得加上FOR UPDATE

另外的话,我们发现其实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 {
        // 获取元数据
        TableMeta tmeta = getTableMeta();
        // 没有前镜像,也不存在后镜像,说明没有数据被修改
        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);
            // 执行查询后镜像
            rs = pst.executeQuery();
            // 包装查询结果
            return TableRecords.buildRecords(tmeta, rs);
        } finally {
            IOUtil.close(rs);
        }
    }

后镜像的生成原理与前镜像的生成原理差不多,不过还是有一些小小的区别的:

1.后镜像的查询条件使用的是前镜像对应的主键值,就没有用业务SQL的查询条件;不同的Executor处理方式不同,需要根据具体的业务SQL来区分;

2.查询后镜像的SQL没有使用FOR UPDATE加锁,直接拿的快照数据;

小结

通过对seata源码的分析,我们现在已经了解了前后镜像的生成原理了:

1.通过业务SQL来判断SQL语句的类型,从而选择不同的Executor来获取前后镜像;

2.前镜像是通过业务SQL的查询条件,并加上FOR UPDATE来查询业务SQL执行前的数据;(不同的Executor实现不同)

3.后镜像是在业务SQL执行完毕后,根据前镜像内的主键数据来获取的数据;(不同的Executor实现不同)

4.通过前后镜像的多种实现可以判断出seata AT模式所支持的SQL语句的所有类型;

以上就是Seata AT模式前后镜像是如何生成详解的详细内容,更多关于Seata AT模式生成前后镜像的资料请关注脚本之家其它相关文章!

相关文章

  • 一文带你搞懂Java中i++ 和 ++i的区别

    一文带你搞懂Java中i++ 和 ++i的区别

    在Java中,i++和++i都用于递增变量i的值,但它们之间有一个细微的区别,i++是后缀递增操作符,++i是前缀递增操作符,在大多数情况下,这两种递增操作的结果都是一样的,但在某些特定的表达式和逻辑中,它们可能会产生不同的效果,本文将带大家搞清Java中i++ 和 ++i的区别
    2023-09-09
  • Java单机环境实现定时任务技术

    Java单机环境实现定时任务技术

    这篇文章主要介绍了Java单机环境实现定时任务技术,文章内容介绍详细,具有一定的参考价值,需要的小伙伴可以参考一下
    2022-04-04
  • SpringBoot 实战 之 优雅终止服务的方法

    SpringBoot 实战 之 优雅终止服务的方法

    本篇文章主要介绍了SpringBoot 实战 之 优雅终止服务的方法,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-05-05
  • Java自定义Enum的实现示例

    Java自定义Enum的实现示例

    Java中的自定义Enum类型是一种特殊的类,用于表示固定数量的常量值,本文主要介绍了Java自定义Enum的实现示例,具有一定的参考价值,感兴趣的可以了解一下
    2023-12-12
  • Javaweb中使用Jdom解析xml的方法

    Javaweb中使用Jdom解析xml的方法

    Jdom是一个开源项目,基于树形结构,利用纯java的技术对XML文档实现解析,生成,序列化以及多种操作.这篇文章主要介绍了Javaweb中使用Jdom解析xml的方法的相关资料,需要的朋友可以参考下
    2016-09-09
  • Java编写简单猜数游戏

    Java编写简单猜数游戏

    这篇文章主要为大家详细介绍了Java编写简单猜数游戏,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-01-01
  • Spring Boot中的@EnableAutoConfiguration注解详解

    Spring Boot中的@EnableAutoConfiguration注解详解

    这篇文章主要介绍了Spring Boot中的@EnableAutoConfiguration注解详解,Spring Boot是一个非常流行的Java框架,它可以快速创建基于Spring的应用程序。Spring Boot提供了许多自动配置功能,使得开发者可以非常容易地创建一个可运行的应用程序,需要的朋友可以参考下
    2023-08-08
  • Java 反射机制的实例详解

    Java 反射机制的实例详解

    这篇文章主要介绍了Java 反射机制的实例详解的相关资料,希望通过本文能帮助到大家,让大家理解掌握反射机制,需要的朋友可以参考下
    2017-10-10
  • Java字符判断的小例子

    Java字符判断的小例子

    从键盘上输入一个字符串,遍历该字符串中的每个字符,若该字符为小写字母,则输出“此字符是小写字母”;若为大写字母,则输出“此字符为大写字母”;否则输出“此字符不是字母”
    2013-09-09
  • ZooKeeper官方文档之Java客户端开发案例翻译

    ZooKeeper官方文档之Java客户端开发案例翻译

    网上有很多ZooKeeper的java客户端例子,我也看过很多,不过大部分写的都不好,有各种问题。兜兜转转还是觉得官方给的例子最为经典,在学习之余翻译下来,供朋友们参考
    2022-01-01

最新评论