Spring事务原理解析

 更新时间:2022年12月20日 09:22:10   作者:码畜c  
Spring事务有可能会提交,回滚、挂起、恢复,所以Spring事务提供了一种机制,可以让程序员来监听当前Spring事务所处于的状态,这篇文章主要介绍了Spring底层事务原理,需要的朋友可以参考下

前言

最近在编写公司APP产品的商品砍价功能,其中有一个接口涉及并发访问。自测时通过ApiFox接口管理工具进行压测,落地数据时出现了"锁失效"的情景。十分感谢后端小伙伴的帮助排查,解决了这个问题。

问题描述

并发接口中,先对主表数据进行读取,进行业务判断后,新增、修改它表的数据。在理应串行执行的情况下发生了多个请求线程读取到了相同的主表数据,导致数据处理异常。也正是前言中所说的"锁失效"了。(实际情况加锁操作是有效的)

代码复现

@RequestMapping("/test")
@Transactional(rollbackFor = Exception.class)
public String test() {
    DistributedLock.lock("ct_lock");
    try {
        Map<String, Object> resultMap = jdbcTemplate.queryForMap("select * from concurrent_read_uncommit");
        int num = Integer.parseInt(resultMap.get("num").toString());
        num++;
        jdbcTemplate.update("update concurrent_read_uncommit set num = " + num);
    } finally {
        DistributedLock.unlock("ct_lock");
    }
    return "success";
}
  • 最少的代码进行演示,Controller方法体中的内容应是Service中的代码
  • DistributedLock中封装的Redission
  • 通过将先读后改的方式演示,实际中本质就是进行了这样的操作,但会存在更多的业务代码(不演示新增的情况)

ApiFox中通过创建100个请求线程进行压测,最终concurrent_read_uncommit表中的num字段值为94,而非100。

排查

1. 锁失效

新编写了两个简单接口,第一个接口加锁,并线程休眠30秒后释放锁。另一个接口加同样的锁,打印一条语句后直接返回。先调用第一个接口,在调用第二个接口。Debug中发现锁是有效的,在redis中存有锁Key。并且访问第二个接口时,线程被阻塞在了加锁行代码。

2. 事务隔离级别

查询数据库事务默认隔离级别:

select @@tx_isolation;

结果

REPEATABLE-READ

就是默认的RR级别,那么说明同个事务内多次读取数据都会是一样的,不会读取到脏数据。

3. 修改Spring事务传播配置

@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW)

与这个并没有关系,八竿子打不着。当时的想法时是多个并发请求在进入到了同个事务内,并一起读取到了没有被修改前的数据。细想想:

  • 事务传播配置一般用在不同事务方法间产生调用时的事务决策,是共用事务还是新创建事务,亦或是其他的方式进行处理
  • test方法本身为根方法,也没有调用其他的事务方法,所以无需配置事务传播配置
  • 即便不在同一事务内,依旧能查询到其他事务修改但未提交的相同数据

解决方案

在锁代码块中调用事务方法,而不是在事务方法中进行加锁。

原因为:并发情境下,执行速度过快,很有可能发生:请求线程在释放锁后没有来得及提交事务,另一个请求线程在加锁处被唤醒,继而读取到了事务未提交的数据。即读取到了脏数据,产生了"锁失效"的效果。

修正代码:

@RequestMapping("/test2")
public String test2() {
    ConcurrentTransactionalController proxyBean = SpringContextUtils.getBean(this.getClass());
    proxyBean.doTest2();
    return "success";
}
@Transactional(rollbackFor = Exception.class)
public void doTest2() {
    DistributedLock.lock("ct_lock");
    try {
        Map<String, Object> resultMap = jdbcTemplate.queryForMap("select * from concurrent_read_uncommit");
        int num = Integer.parseInt(resultMap.get("num").toString());
        num++;
        jdbcTemplate.update("update concurrent_read_uncommit set num = " + num);
        Thread.sleep(500);
    } catch (InterruptedException e) {
        throw new RuntimeException(e);
    } finally {
        DistributedLock.unlock("ct_lock");
    }
}
  • 将需要加锁的事务代码进行提取另一个方法
  • 调用方法中进行加锁,并且必须要去掉事务注解
  • 因为是在非事务方法调用事务方法,为了保证事务生效,需要通过事务代理Bean进行调用

这样就保证了不会读取到事务未提交的数据,同时又具有锁的排他性。

其实锁一直都是有效的,本质原因就在于Spring的事务代理Bean屏蔽了事务代码。我们不能手动的进行控制,也就是说你变更了不了事务代码的顺序。如果能将提交事务的行代码写到释放锁之前,就不会存在这个问题了。所以,也可以通过编程式事务解决这个问题,关于编程式事务,Spring也有做代码封装。如果不通过编程式事务,那么就只能通过上述代码变相的来实现。

到此这篇关于Spring事务原理解析的文章就介绍到这了,更多相关Spring事务内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Java 多线程synchronized关键字详解(六)

    Java 多线程synchronized关键字详解(六)

    这篇文章主要介绍了Java 多线程synchronized关键字详解(六)的相关资料,需要的朋友可以参考下
    2015-12-12
  • Spring Boot文件上传最新解决方案

    Spring Boot文件上传最新解决方案

    本文给大家分享Spring Boot文件上传功能的示例代码,包括单文件上传示例和多文件上传,代码简单易懂,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧
    2021-07-07
  • Mybatis结果集映射与生命周期详细介绍

    Mybatis结果集映射与生命周期详细介绍

    结果集映射指的是将数据表中的字段与实体类中的属性关联起来,这样 MyBatis 就可以根据查询到的数据来填充实体对象的属性,帮助我们完成赋值操作
    2022-10-10
  • 这一次搞懂Spring自定义标签以及注解解析原理说明

    这一次搞懂Spring自定义标签以及注解解析原理说明

    这篇文章主要介绍了这一次搞懂Spring自定义标签以及注解解析原理说明,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-08-08
  • java ReentrantLock并发锁使用详解

    java ReentrantLock并发锁使用详解

    这篇文章主要为大家介绍了java ReentrantLock并发锁使用详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-10-10
  • Java中数组的常见操作合集

    Java中数组的常见操作合集

    这篇文章主要为大家详细介绍了Java中数组的一些常见操作,例如:数组遍历、数组获取最大值元素、数组反转等,感兴趣的小伙伴可以了解一下
    2022-10-10
  • IDEA使用JDBC安装配置jar包连接MySQL数据库

    IDEA使用JDBC安装配置jar包连接MySQL数据库

    这篇文章介绍了IDEA使用JDBC安装配置jar包连接MySQL数据库的方法,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2022-01-01
  • Java中java.lang.ClassCastException异常原因及解决方法

    Java中java.lang.ClassCastException异常原因及解决方法

    大家好,本篇文章主要讲的是Java中java.lang.ClassCastException异常原因及解决方法,感兴趣的同学赶快来看一看吧,对你有帮助的话记得收藏一下
    2022-01-01
  • SpringBoot Profiles 多环境配置及切换

    SpringBoot Profiles 多环境配置及切换

    大部分情况下,我们开发的产品应用都会根据不同的目的,所以需要支持不同的环境,本文主要介绍了SpringBoot Profiles 多环境配置及切换,感兴趣的可以了解一下
    2021-12-12
  • Java 泛型实例详解

    Java 泛型实例详解

    本文主要介绍Java 泛型的知识,这里给代码实例对Java 泛型深度理解,有需要的朋友可以看下
    2016-07-07

最新评论