【事务与并发】- 不同事务读取相同数据问题

简介: 在加了事务的接口中,不同的业务或者是出现并发的时候,发现了一些SQL读取问题,两个都被事务包裹的方法,各自是隔离的,如果一方的事务延时提交,就会导致另一方读取出来的数据相同,并不是修改后的数据。

前言

最近工作中,在加了事务的接口中,不同的业务或者是出现并发的时候,发现了一些SQL读取问题,两个都被事务包裹的方法,各自是隔离的,如果一方的事务延时提交,就会导致另一方读取出来的数据相同,并不是修改后的数据。

问题描述

不同业务涉及到同一张表的时候,或者相同业务出现并发访问时,可能会导致读取到的数据是相同的。在业务a中,读取表的数据1,并且进行修改,在将数据2写回数据库,此时事务并未提交。但是业务b就从数据库中又获取了数据1(期望来说要获取的是数据2),这就导致了两个事务获取的数据相同了,这个显然就是不对的。

案例准备

本次采用springboot+mybatis-plus来做案例,基础简单就不再赘述。首先需要准备一张表,本次模拟开发业务中的情况,需要一张点位表(t_point),就包括四个字段就行,其中控制is_lock来判断是否锁定。

业务需求

本次模拟点位查找的业务,根据is_lock来判断改点是否被锁住,需要查找没有被锁住的点位,进行处理之后,将点位设置未锁定,然后写回数据库,本次用两个方法查找同一个点位来模拟业务a、业务b(creatA,creatB方法),并且两个业务需要用事务包裹。

问题回放

我们在creatA方法中在更新之后睡眠了10s,来模拟事务延时提交。当我们访问creatA和creatB,就会看到两个方法都找到了点位是没锁的。

1)、代码复现

creatA方法是从点位表中获取未锁得数据,进行判断是否锁住,没锁就上锁然后更新到数据库中,然后睡眠10s模拟事务延时提交;creatB方法也是相同的,只是不需要加上睡眠。

@Override@Transactional(rollbackFor=Exception.class)
publicvoidcreatA() {
// 查找未锁定Pointpoint=pointMapper.selectOne(newLambdaQueryWrapper<Point>()
            .eq(Point::getPointCode, "A")
            .eq(Point::getIsLock, false));
if (ObjectUtil.isEmpty(point)) {
// 锁了System.out.println("CreateA -> 点位已经上锁");
return;
    }
// 处理一些业务......System.out.println("CreateA正在处理相应业务");
// 锁定point.setIsLock(true);
// 更新pointMapper.updateById(point);
// 休眠:模拟延时提交事务try {
Thread.sleep(10000);
    } catch (InterruptedExceptione) {
thrownewRuntimeException(e);
    }
}

然后通过两个接口,各自调用方法

@GetMapping("/t1")
publicvoidcreateA() {
pointService.creatA();
}
@GetMapping("/t2")
publicvoidcreateB() {
pointService.creatB();
}

2)、运行结果

先访问creatA,然事务卡住不提交,在访问creatB,就会发现,他们两个查到的数据都是未锁,拿到的是更新前的数据。

3)、问题分析

首先,因为两个方法都被事务给包裹住了,这就导致需要等待事务提交之后,才算真正的写回到数据库中。Spring中使用@Transactional注解的事务是相互隔离的。Spring框架使用底层的事务管理器来管理事务,这些事务管理器支持不同的事务隔离级别。当使用@Transactional注解时,可以通过指定不同的隔离级别来控制事务的隔离程度。所以createB在createA事务提交之前,是看不到修改的数据的。

4)、解决方法

解决上述方式可以直接使用for update(排它锁),在查询数据添加for update,防止其他事务同时修改这些数据,从而保证事务的一致性。

需要注意的是,使用FOR UPDATE语句需要在事务内部执行,否则该语句不会起作用。此外,在使用FOR UPDATE时,需要保证被锁定的记录的索引列上有足够的索引,否则查询可能会导致表级锁定,从而降低并发性能。因此我们需要在表中添加索引。

Pointpoint=pointMapper.selectOne(newLambdaQueryWrapper<Point>()
        .eq(Point::getPointCode, "A")
        .eq(Point::getIsLock, false)
        .last("for update"));

当查询这行数据的时候,就会给记录加上排它锁,使其他事务无法同时修改或删除这条记录,直到当前事务释放锁(当事务提交、回滚、超时或被杀死)。在并发环境下,如果多个事务同时请求加锁,可能会出现环路等待的情况,导致所有事务都被阻塞无法继续执行。加锁策略还是要合理设计,否则可能出现死锁问题。



👍创作不易,如有错误请指正,感谢观看!记得点赞哦!👍

目录
打赏
0
0
0
0
14
分享
相关文章
RabbitMQ之消息应答和持久化
【1月更文挑战第11天】 一、消息应答 1.概念 2.自动应答 3.消息应答方法 4.Multiple 的解释 5.消息自动重新入队 6.消息手动应答代码 7.手动应答效果演示 二、RabbitMQ持久化 1.概念 2.队列如何实现持久化 3.消息实现持久化 4.不公平分发 5.预取值
427 52
Apache Spark:提升大规模数据处理效率的秘籍
【4月更文挑战第7天】本文介绍了Apache Spark的大数据处理优势和核心特性,包括内存计算、RDD、一站式解决方案。分享了Spark实战技巧,如选择部署模式、优化作业执行流程、管理内存与磁盘、Spark SQL优化及监控调优工具的使用。通过这些秘籍,可以提升大规模数据处理效率,发挥Spark在实际项目中的潜力。
902 0
Java一分钟之-JPA注解:@Entity, @Table, @Id等
【6月更文挑战第14天】Java Persistence API (JPA) 是Java开发中的ORM框架,通过注解简化数据访问层。本文介绍了三个核心注解:`@Entity`标识实体类,`@Table`自定义表名,`@Id`定义主键。易错点包括忘记添加`@Entity`、未正确设置主键。建议使用`@GeneratedValue`和`@Column`细化主键策略和字段映射。正确理解和应用这些注解能提高开发效率和代码质量。
846 3
数字图像处理实验(四)|图像压缩与编码实验{JPGE编码、离散余弦变换DCT、图像分块dctmtx|blkproc}(附matlab实验代码和截图)
数字图像处理实验(四)|图像压缩与编码实验{JPGE编码、离散余弦变换DCT、图像分块dctmtx|blkproc}(附matlab实验代码和截图)
1240 0
数字图像处理实验(四)|图像压缩与编码实验{JPGE编码、离散余弦变换DCT、图像分块dctmtx|blkproc}(附matlab实验代码和截图)
了解编程语言中的类型安全
【7月更文挑战第15天】本文介绍类型安全与效率在语言设计中至关重要。安全性需求各异,从HIPAA到PCI合规,选择最佳技术栈要考虑多方面,包括微服务架构中的语言多样性,以确保每个服务都能高效且安全地执行其特定任务。
281 14
了解编程语言中的类型安全
昆仑万维「天工」Skywork-13B魔搭社区首发开源!魔搭最佳实践来了!
作为国内最具诚意的开源百亿大模型,「天工」Skywork-13B系列无需申请即可实现商用,授权链路极简,且无用户数、行业、公司规模限制。
【专栏】网络高可用性和负载均衡关键在于VRRP、VGMP和HRP协议
【4月更文挑战第28天】网络高可用性和负载均衡关键在于VRRP、VGMP和HRP协议。VRRP实现路由器冗余,保证流量转发;VGMP优化多播流量传输,适合多媒体服务;HRP提供无缝故障转移,适用于电信级网络。选择需考虑网络环境和业务需求,VRRP简单易部署,VGMP处理多播流量,HRP适合高稳定性场景。理解协议特点,确保网络最佳性能和可用性。
495 4

热门文章

最新文章

AI助理

你好,我是AI助理

可以解答问题、推荐解决方案等

登录插画

登录以查看您的控制台资源

管理云资源
状态一览
快捷访问