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

简介: 在事务中,使用了 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


相关文章
|
存储 关系型数据库 MySQL
MySQL 处理大数据表的 3 种方案,写的太好了,建议收藏!!
MySQL 处理大数据表的 3 种方案,写的太好了,建议收藏!!
960 0
|
消息中间件 存储 算法
RocketMQ 重试机制详解及最佳实践
本文主要介绍在使用 RocketMQ 时为什么需要重试与兜底机制,生产者与消费者触发重试的条件和具体行为,如何在 RocketMQ 中合理使用重试机制,帮助构建弹性,高可用系统的最佳实践。
1645 0
RocketMQ 重试机制详解及最佳实践
|
11月前
|
NoSQL Java Redis
太惨痛: Redis 分布式锁 5个大坑,又大又深, 如何才能 避开 ?
Redis分布式锁在高并发场景下是重要的技术手段,但其实现过程中常遇到五大深坑:**原子性问题**、**连接耗尽问题**、**锁过期问题**、**锁失效问题**以及**锁分段问题**。这些问题不仅影响系统的稳定性和性能,还可能导致数据不一致。尼恩在实际项目中总结了这些坑,并提供了详细的解决方案,包括使用Lua脚本保证原子性、设置合理的锁过期时间和使用看门狗机制、以及通过锁分段提升性能。这些经验和技巧对面试和实际开发都有很大帮助,值得深入学习和实践。
太惨痛: Redis 分布式锁 5个大坑,又大又深, 如何才能 避开 ?
|
设计模式 Java 关系型数据库
【Java笔记+踩坑汇总】Java基础+JavaWeb+SSM+SpringBoot+SpringCloud+瑞吉外卖/谷粒商城/学成在线+设计模式+面试题汇总+性能调优/架构设计+源码解析
本文是“Java学习路线”专栏的导航文章,目标是为Java初学者和初中高级工程师提供一套完整的Java学习路线。
805 37
|
缓存 Java 关系型数据库
【Java面试题汇总】ElasticSearch篇(2023版)
倒排索引、MySQL和ES一致性、ES近实时、ES集群的节点、分片、搭建、脑裂、调优。
【Java面试题汇总】ElasticSearch篇(2023版)
|
存储 SQL 关系型数据库
MySQL高级篇——索引失效的11种情况
索引优化思路、要尽量满足全值匹配、最佳左前缀法则、主键插入顺序尽量自增、计算、函数导致索引失效、类型转换(手动或自动)导致索引失效、范围条件右边的列索引失效、不等于符号导致索引失效、is not null、not like无法使用索引、左模糊查询导致索引失效、“OR”前后存在非索引列,导致索引失效、不同字符集导致索引失败,建议utf8mb4
MySQL高级篇——索引失效的11种情况
|
存储 SQL 关系型数据库
【MySQL调优】如何进行MySQL调优?从参数、数据建模、索引、SQL语句等方向,三万字详细解读MySQL的性能优化方案(2024版)
MySQL调优主要分为三个步骤:监控报警、排查慢SQL、MySQL调优。 排查慢SQL:开启慢查询日志 、找出最慢的几条SQL、分析查询计划 。 MySQL调优: 基础优化:缓存优化、硬件优化、参数优化、定期清理垃圾、使用合适的存储引擎、读写分离、分库分表; 表设计优化:数据类型优化、冷热数据分表等。 索引优化:考虑索引失效的11个场景、遵循索引设计原则、连接查询优化、排序优化、深分页查询优化、覆盖索引、索引下推、用普通索引等。 SQL优化。
1471 15
【MySQL调优】如何进行MySQL调优?从参数、数据建模、索引、SQL语句等方向,三万字详细解读MySQL的性能优化方案(2024版)
|
缓存 Java 开发工具
Spring是如何解决循环依赖的?从底层源码入手,详细解读Spring框架的三级缓存
三级缓存是Spring框架里,一个经典的技术点,它很好地解决了循环依赖的问题,也是很多面试中会被问到的问题,本文从源码入手,详细剖析Spring三级缓存的来龙去脉。
892 24
Spring是如何解决循环依赖的?从底层源码入手,详细解读Spring框架的三级缓存
|
存储 缓存 监控
【JVM调优】如何进行JVM调优?一篇文章就够了!
深入解读JVM性能的监控、定位和调优方案,阐述jps/stat/jstack、MAT等常用性能分析工具的使用,提出JVM参数、内存溢出、内存泄漏、CPU飙升、GC频繁等实际场景下JVM调优的方案。
【JVM调优】如何进行JVM调优?一篇文章就够了!