SpringBoot 商城系统高并发引起的库存超卖库存问题 乐观锁 悲观锁 抢购 商品秒杀 高并发

介绍

在高并发场景下,特别是商品秒杀、抢购等情况下,库存超卖问题是一个常见且棘手的问题。为了解决这个问题,Spring Boot 常使用乐观锁和悲观锁来保证数据的正确性和一致性。

悲观锁

悲观锁假设在多线程或多进程环境中,资源会被其他线程频繁修改,因此会尽量避免冲突,通常会在访问共享资源时加锁,直到操作完成才释放锁。这样其他线程在此期间无法访问该资源。

  • 锁粒度大:悲观锁通常是在访问资源前就加锁,直到操作完成后才解锁。
  • 性能较低:由于锁的存在,可能会导致线程等待时间较长,影响系统的并发性能。
  • 适用于资源冲突较多的场景,如银行账户转账操作。

乐观锁

乐观锁则假设在多线程环境下,资源冲突的概率较低,因此不直接加锁,而是允许多个线程并发访问共享资源。乐观锁通常依赖于数据版本控制或冲突检测来避免数据冲突。只有在提交操作时,才会检查是否有冲突发生,如果没有冲突,则提交成功;如果有冲突,则放弃或重试。

  • 锁粒度小:只有在提交数据时才进行冲突检测。
  • 性能较高:因为没有频繁加锁,线程可以并行处理多个操作,提高了系统的并发能力。
  • 适用于冲突较少的场景,如读取多、写入少的系统。

实现方式

版本号机制

乐观锁中常用的一种方式,它通过在每一条数据记录中添加一个版本号字段来判断数据是否被修改。

时间戳机制

它通过记录每条数据的最后更新时间(时间戳),来判断数据是否被修改过。


无锁测试

50个库存,200线程测试高并发。

超卖出40件。


悲观锁测试

java 复制代码
   private final GoodsMapper goodsMapper;
    @Override
    @SneakyThrows
    public int secKillGood(int goodId)   {

        synchronized(this) {  // 悲观加锁,确保同一时刻只有一个线程进入该代码块,其他进来线程会被卡

        Goods goods =goodsMapper.selectById(goodId);
        if(goods.getStock()<1){
            return 0;
        }


        LambdaUpdateWrapper<Goods> goodsLambdaQueryWrapper =new LambdaUpdateWrapper<>();
        goodsLambdaQueryWrapper.eq(Goods::getId,goodId).setSql("stock = stock-1");

      return   goodsMapper.update(null,goodsLambdaQueryWrapper);
        }
    }

解决了超卖问题,但是在高并发的场景下如果耗时比较久,其他的请求将会被卡死,直到该锁被释放。性能低下。


乐观锁

查询商品库存信息和版本号 -》 判断库存是否大于0 -》 扣减库存 AND 版本号等于= 刚查询出来的版本号

如果版本号一直更新成功了,负责就是被其他线程抢了,AND条件不成立,更新失败。

在商城系统中可以使用 库存 来代替 版本号

java 复制代码
@Override
@SneakyThrows
public int secKillGood(int goodId)   {

    Goods goods =goodsMapper.selectById(goodId);
    if(goods.getStock()<1){
        return 0;
    }

    LambdaUpdateWrapper<Goods> goodsLambdaQueryWrapper =new LambdaUpdateWrapper<>();
    goodsLambdaQueryWrapper.eq(Goods::getId,goodId).setSql("stock = stock-1") //扣减库存
            .eq(Goods::getStock,goods.getStock())
            .gt(Goods::getStock,0); //扣减时库存大于1 还有库存

  return   goodsMapper.update(null,goodsLambdaQueryWrapper);
    }

尽管解决了超卖的问题,但是在高并发的场景下需要频繁的访问数据还会导致性能不足,在数据量特别大的情况下应该使用其他的中间件来降低数据的增删改查。

相关推荐
yuniko-n6 小时前
【JUC】集合线程安全
java
liulilittle6 小时前
Linux 下 select 测试函数
linux·服务器·网络·数据库·c++·select·c
DongQiShanRen6 小时前
玄龙(上):TICK 主循环——意识心跳怎么跳
linux·jvm·数据库·人工智能·数据挖掘·rust
骑着蜗牛撵大象3276 小时前
服务下线不再炸:分层关闭、TCP 存活探测与负载均衡排水的落地套路
android·tcp/ip·负载均衡·tcp·高可用·健康检查·优雅关闭
数据库小学妹6 小时前
存算分离到底分离了什么?四条架构变化与选型判断
数据库·数据库架构·云原生数据库·缓存一致性·存算分离
FPGA小徐7 小时前
【一生一芯 / PA】异常响应机制:RISC-V 中 ecall → mtvec → mret 的完整代
开发语言·数据库·c#
starzy19907 小时前
Flink SlotManager启动流程源码深度剖析:从ResourceManager到Slot分配的完整链路
java·大数据·flink
极光通讯7 小时前
服务器内存来料检验(IQC)实操:批次追溯、外观判定与上机验证
java·服务器·算法
4SAPI7 小时前
AI API Gateway平台哪个好?从架构视角看企业多模型网关选型与实践
java·大数据·人工智能·gateway·php
EatFan7 小时前
2026 后端 AI 工程化:Spring AI 2.0、MCP 协议与 Agent 内嵌如何收进 Java 生产系统
java·人工智能·spring·agent·spring ai·spring boot 3·mcp