【幂等性大坑】事务提交前释放锁导致锁失效问题

简介: 在事务中,使用了 Redis 分布式锁.这个方法一旦执行,事务生效,接着就 Redis 分布式锁生效,代码执行完后,先释放 Redis 分布式锁,然后再提交事务数据,最后事务结束。如果是表单重复提交场景,可以尝试给“订单号”等有唯一性的字段加唯一索引,这样重复提交时会因为唯一索引约束导致索引失效。5、如果表的一个字段,要作为另外一个表的外键,这个字段必须有唯一约束(或是主键),如果只是有唯一索引,就会报错。2、创建唯一约束,会自动创建一个同名的唯一索引,该索引不能单独删除,删除约束会自动删除索引。

 导航:

【Java笔记+踩坑汇总】Java基础+JavaWeb+SSM+SpringBoot+SpringCloud+瑞吉外卖/谷粒商城/学成在线+设计模式+面试题汇总+性能调优/架构设计+源码解析

目录

一、问题分析

1.1 幂等性失效导致重复提交表单问题

1.2 秒杀超卖问题

二、解决方案

2.1 方案一:加唯一索引

2.2 方案二:事务外层加锁

2.3 方案三:嵌套事务


一、问题分析

1.1 幂等性失效导致重复提交表单问题

问题:高并发下,系统负载大,采用分布式锁实现幂等性时,在解锁到提交事务期间,其他线程获取到锁并提交事务。

结果:导致同一用户插入了两条相同的数据。

伪代码模拟:

@Transactional
public void submit() {
    boolean lockFlag=lock();    //例如setnx
    if(!lockFlag) {
//加锁失败
        throw new RuntimeException(“请稍后再试”);
    }
//加锁成功
    if(!dao.query())    //如果查不到数据
    dao.insert();    //则插入一条数据
    unlock();
//解锁后,事务还没来得及提交,此线程就阻塞了。
//此时另一个线程成功获取、查数据(数据依然不存在,因为上个线程的事务并没有提交)插数据、释放锁、提交事务。
}
//此线程执行完毕,又提交了一次事务。导致两个线程都成功插入了数据。

image.gif

在事务中,使用了 Redis 分布式锁.这个方法一旦执行,事务生效,接着就 Redis 分布式锁生效,代码执行完后,先释放 Redis 分布式锁,然后再提交事务数据,最后事务结束。在这个过程中,事务没有提交之前,分布式锁已经被释放, 导致分布式锁失效。

1.2 秒杀超卖问题

案例一:

加锁{
    查表
    取值
    更新
}
释放锁

image.gif

以线程A和B为例:

  1. 线程A得到锁,
  2. 线程A查看user表得到账户余额,,
  3. 线程A加上前端传来的余额,
  4. 线程A更新数据库。
  • 开启事务
  • 执行更新语句(注意此时程序顺序执行释放锁,线程B获取锁)
  • 线程B获取锁,
  • 查询user表获得未更新前的账户余额,
  • 提交事务
  1. 线程B加上前端传来的余额,
  2. 线程B更新数据库。

案例二:

@Transactional
public void seckill() {
    boolean lockFlag=lock();    //例如setnx
    if(!lockFlag) {
//加锁失败
        throw new RuntimeException(“请稍后再试”);
    }
//加锁成功
    if(dao.queryStock()>0){    //如果查询库存有余额。例如余额是1
        dao.updateStock();    //则减库存。此时余额是0
    }
    unlock();
    //此时解锁成功了,因为事务还没有提交,此线程又阻塞了,此时另一个线程成功获取释放锁、查询库存是1(因为读不到未提交事务的数据),就减库存提交事务。
}
//此线程执行完毕,因为没有异常,所以又提交了一次事务,导致多卖了一次商品

image.gif

二、解决方案

2.1 方案一:加唯一索引

如果是表单重复提交场景,可以尝试给“订单号”等有唯一性的字段加唯一索引,这样重复提交时会因为唯一索引约束导致索引失效。

使用UNIQUE参数可以设置索引为唯一性索引,在创建唯一性索引时,限制该索引的值必须是唯一的,但允许有多个空值。在一张数据表里可以有多个唯一索引。

唯一约束和唯一索引的区别:

1、唯一约束和唯一索引,都可以实现列数据的唯一,列值可以有null。

2、创建唯一约束,会自动创建一个同名的唯一索引,该索引不能单独删除,删除约束会自动删除索引。唯一约束是通过唯一索引来实现数据的唯一。

3、创建一个唯一索引,这个索引就是独立,可以单独删除。

4、如果一个列上想有约束和索引,且两者可以单独的删除。可以先建唯一索引,再建同名的唯一约束。

5、如果表的一个字段,要作为另外一个表的外键,这个字段必须有唯一约束(或是主键),如果只是有唯一索引,就会报错。

2.2 方案二:事务外层加锁

  • 分布式锁在controller层中添加,事务在service层中添加。
  • 使用编程式事务,外层是锁,内层是事务。

2.3 方案三:嵌套事务

将查表更新表的操作单独封装成一个方法(在事务外面加锁)。然后加上spring事务(嵌套提交)。

@Transactional
public void submit() {
    if(lock()){
//提交表单的业务逻辑会生成一个嵌套事务,子事务提交回滚独立于外层事务。
        xxxService.submitAfterUnLock();    //注意别用this调用,会失效。
    }
    unlock();
    
    //此时另一个线程,
}
@Transactional(propagation = Propagation.NESTED)    //嵌套事务
public void submitAfterUnLock() {
    if(!dao.query()) insert();//如果查不到表单(通过订单号),则提交表单。
}

image.gif


相关文章
|
SQL 关系型数据库 数据库
学习分布式事务Seata看这一篇就够了,建议收藏
学习分布式事务Seata看这一篇就够了,建议收藏
26242 2
|
NoSQL Redis 数据库
阿里云Redis集群版简要介绍
产品简介 云数据库 Redis 提供集群版实例,轻松突破 Redis 自身单线程瓶颈,可极大满足对于 Redis 大容量或高性能的业务需求。 云数据库 Redis 集群版内置数据分片及读取算法,整体过程对用户透明,免去用户开发及运维 Redis 集群的烦恼。
15475 0
太惨痛: Redis 分布式锁 5个大坑,又大又深, 如何才能 避开 ?
Redis分布式锁在高并发场景下是重要的技术手段,但其实现过程中常遇到五大深坑:**原子性问题**、**连接耗尽问题**、**锁过期问题**、**锁失效问题**以及**锁分段问题**。这些问题不仅影响系统的稳定性和性能,还可能导致数据不一致。尼恩在实际项目中总结了这些坑,并提供了详细的解决方案,包括使用Lua脚本保证原子性、设置合理的锁过期时间和使用看门狗机制、以及通过锁分段提升性能。这些经验和技巧对面试和实际开发都有很大帮助,值得深入学习和实践。
太惨痛: Redis 分布式锁 5个大坑,又大又深, 如何才能 避开 ?
|
Oracle Java 关系型数据库
SpringBoot从0-1集成Graalvm
本文介绍如何使用GraalVM将SpringBoot应用打包为原生可执行文件并构建Docker镜像。相比传统JAR包,原生镜像启动更快、体积更小,提升部署效率,适合现代云原生环境。
1285 10
|
监控 NoSQL 算法
Redis主从切换,锁失效怎么办?
在分布式系统中,Redis因其高性能和易用性而被广泛应用于缓存、分布式锁等场景。然而,当Redis采用主从架构以实现高可用性和数据冗余时,主从切换可能带来的锁失效问题成为了一个不容忽视的挑战。本文将深入探讨Redis主从切换导致锁失效的原因、影响及解决方案,旨在为大家提供实用的技术干货。
1036 5
|
SQL XML Java
8、Mybatis-Plus 分页插件、自定义分页
这篇文章介绍了Mybatis-Plus的分页功能,包括如何配置分页插件、使用Mybatis-Plus提供的Page对象进行分页查询,以及如何在XML中自定义分页SQL。文章通过具体的代码示例和测试结果,展示了分页插件的使用和自定义分页的方法。
8、Mybatis-Plus 分页插件、自定义分页
|
数据库
如何解决逻辑删除is_del与数据库唯一约束冲突
如何解决逻辑删除is_del与数据库唯一约束冲突
808 0
|
存储 消息中间件 Java
深入理解Spring的TransactionSynchronizationManager
深入理解Spring的TransactionSynchronizationManager
2749 0
|
Java 数据库连接 数据库
springboot java.lang.ClassNotFoundException: dm.jdbc.driver.DmDriver应该如何解决
通过上述步骤,可以有效解决Spring Boot项目中遇到的 `java.lang.ClassNotFoundException: dm.jdbc.driver.DmDriver`问题。确保在项目中正确添加达梦数据库的JDBC驱动依赖,并在配置文件中正确配置数据源信息,是解决此问题的关键。通过这些方法,可以确保Spring Boot项目能够正确连接达梦数据库并正常运行。
3023 31
|
缓存 安全 Java
Spring框架中Bean是如何加载的?从底层源码入手,详细解读Bean的创建流程
从底层源码入手,通过代码示例,追踪AnnotationConfigApplicationContext加载配置类、启动Spring容器的整个流程,并对IOC、BeanDefinition、PostProcesser等相关概念进行解释
3025 26
Spring框架中Bean是如何加载的?从底层源码入手,详细解读Bean的创建流程