生产问题一:redis锁处理幂等性失效

伪代码:

java 复制代码
    @Transactional(rollbackFor = Exception.class)
    public void add(User user) {
        String key = "key";
        RLock lock = redissonClient.getLock(key);
        lock.lock();
        try {
            long count = userMapper.selectCount(user);
            if (count == 0) {
                userMapper.insert(user);
            }
        } catch (Exception e) {
            log.error("add user error", e);
        } finally {
            lock.unlock();
        }
        System.out.println("插入成功");
    }

问题分析:

如果有两个线程a,b。如果a线程释放锁后,退出方法前,让出时间片,由于方法未执行完,此时事务没有提交,那么b线程在去数据库查询的时候仍然出来count为0,执行了insert操作,两个线程执行完该方法提交事务,此时数据库中会增加两条数据,幂等性失效。

注:a事务未提交,查出来count仍然为0的原因是,mysql默认的事物隔离级别是可重复读,因此无法读取为提交的数据

解决方式:

第一种:

java 复制代码
    //@Transactional(rollbackFor = Exception.class)
    public void add(User user) {
        String key = "key";
        RLock lock = redissonClient.getLock(key);
        lock.lock();
        try {
            long count = userMapper.selectCount(user);
            if (count == 0) {
                userMapper.insert(user);
            }
        } catch (Exception e) {
            log.error("add user error", e);
        } finally {
            lock.unlock();
        }
        System.out.println("插入成功");
    }

去掉@Transactional注解,这样在a线程在insert时候就会自动提交事务,a释放锁后,b在查询时候,count不等0,不执行插入操作。

方法二:

java 复制代码
public class Test {

    @Resource
    private UserMapper userMapper;

    @Resource
    private UserService userService;

    @Resource
    private RedissonClient redissonClient;

    public void test(User user) {
        String key = "key";
        RLock lock = redissonClient.getLock(key);
        lock.lock();
        try {
            userService.addOk(user);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    @Transactional(rollbackFor = Exception.class)
    public void addOk(User user) {
        long count = userMapper.selectCount(user);
        if (count == 0) {
            userMapper.insert(user);
        }
    }
}

a提交事务后再释放锁后,b在查询时候,count不等0,不执行插入操作。
绝大数情况下,为了防止重复数据的产生,我们都会在表中加唯一索引,这是一个非常简单,并
且有效的方案。 虽说抛异常对数据来说没有影响,不会造成错误数据。但是为了保证接口幂等性,我们需要对该 异常进行捕获,然后返回成功。
扩展:
事务在生产实践中经常犯的错误:
1.事务范围:应该加入事务的代码未加入到事务中
2.事务大小:事务过大,是否有必要拆解小事务(如何优化),拆解后一致性问题。
3.传播范围: a.多线程之间不可传播;b.多个方法内如果异常被捕获,事务会被被标记为异常事务,不可以再次提交(虽然不影响数据,但是有报错信息)

相关推荐
Swift社区1 小时前
从 JDK 1.8 切换到 JDK 21 时遇到 NoProviderFoundException 该如何解决?
java·开发语言
DKPT2 小时前
JVM中如何调优新生代和老生代?
java·jvm·笔记·学习·spring
phltxy2 小时前
JVM——Java虚拟机学习
java·jvm·学习
seabirdssss4 小时前
使用Spring Boot DevTools快速重启功能
java·spring boot·后端
喂完待续4 小时前
【序列晋升】29 Spring Cloud Task 微服务架构下的轻量级任务调度框架
java·spring·spring cloud·云原生·架构·big data·序列晋升
benben0444 小时前
ReAct模式解读
java·ai
轮到我狗叫了5 小时前
牛客.小红的子串牛客.kotori和抽卡牛客.循环汉诺塔牛客.ruby和薯条
java·开发语言·算法
Volunteer Technology6 小时前
三高项目-缓存设计
java·spring·缓存·高并发·高可用·高数据量
栗子~~6 小时前
bat脚本- 将jar 包批量安装到 Maven 本地仓库
java·maven·jar