文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

Spring事务原理解析

2022-12-20 12:00

关注

前言

最近在编写公司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";
}

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)

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

解决方案

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

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

修正代码:

@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");
    }
}

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

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

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

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     801人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     348人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     311人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     432人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     220人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯