java-防止库存超卖

文章讨论了在高并发场景下,如何通过在数据库中添加版本号实现乐观锁,避免库存扣减冲突的问题,从而提高并发处理的正确性和用户体验。作者介绍了在Spring框架下利用InnoDB存储引擎和事务处理的代码示例。
摘要由CSDN通过智能技术生成

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

文章目录


前言

之前写过一个redis防止库存超卖的:
点这里查看

	@Resource
    private RedisTemplate<String, String> redisTemplate;
    
	@Transactional(rollbackFor = Exception.class)
    private boolean reduceStock(String stockId,Integer reduceQuanity) {
        // 是否加锁成功
        Boolean locked = false;
        try {
        	// 加锁,设置锁超时时间,等待锁的时间
            locked = redisLock.tryLock(stockId, 120,20);
            if (!locked) {
                // 给原来的锁追加时间
                redisLock.reLock(stockId, 120);
                throw new RuntimeException("当前库存有其他订单在操作");
            }
				// 库存是否足够
				if(getStockQuantity(stockId)>reduceQuanity){
				     // 扣减库存
				int count = reduceQuantity(stockId,reduceQuanity);
					if(count==0){
					throw new RuntimeException("库存扣减失败");
					}
				}
            return true;
        } finally {
            // 业务处理成功后释放锁
            if (locked) {
                redisLock.unlock(stockId);
            }
        }
    }
	public Integer getStockQuantity(String stockId){
		// select quantity  from stock  where id = #{stockId}
	}
	public int reduceQuantity(String stockId,Integer quantity){
	            //  update stock set quantity = quantity - #{quantity}        where id = #{stockId} 
	}

以上代码咋一看确实没啥问题,但是如果我们现在需要下了多个商品需要扣减多个商品的库存会发生什么呢?下面是我们的伪代码

 @Transactional(rollbackFor = Exception.class)
	private void sumitOrder() {
		// 扣减商品a库存
		boolean b1 = reduceStock("a",100);
		if(!b1){
			throw  new RuntimeException("库存改变失败");
		}
		// 扣减商品b库存
		boolean b2 =  reduceStock("b",2);
		if(!b2){
			throw  new RuntimeException("库存改变失败");
		}
	}

注意:最大的问题就是,在于并发的情况
现在假设:
1.我们有商品a数量有100个。
2.张三和李四都买了多个商品,他们都要买a商品100个。

同时下单的情况, 张三先扣减商品a库存,释放商品a的锁(代码执行到final)、但张三其他商品没有扣减完,redis锁先释放了但是张三的事务未提交,此时李四也去扣减库李四获取到redis锁以为库存里面还有数量所以会导致我们库存扣减出错。
在这里插入图片描述


数据库修改的时候虽然是有行锁,但是查询的时候没办法保证查询到最新的数据。

实现

解决这个问题也很简单我们只需要给每个库存加上一个版本号,也就是乐观锁

CREATE TABLE `stock` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `version` int(11) DEFAULT NULL,
  `quantity` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8mb4;

修改库存需要传入version,这样即使查询数据不是实时库存,也不会导致我们修改库存时发生错误。
因为修改有行锁,只要进行扣减了库存version = version +1,并发时候后面的人去扣减库存如果传入的version不是最新,就会提示扣减库存失败,但是这样也会导致一个问题,就是库存充足的情况下明明还有库存,但是由于传入的不是最新的version却会导致扣减库存失败,用户体验度不好。
最终代码如下也不需要redis进行锁定了,可以用jmeter压测工具进行测试

 	@Transactional(rollbackFor = Exception.class)
	private void sumitOrder() {
		// a商品库存版本、从数据库查询出来的
		Integer aVersion = 1;
		// 扣减商品a库存
		boolean b1 = reduceQuantity("a", aVersion ,100);
		if(!b1){
			throw  new RuntimeException("库存改变失败");
		}

		// b商品库存版本、从数据库查询出来的
		Integer bVersion = 111;
		// 扣减商品b库存
		boolean b2 =  reduceQuantity("b", bVersion,2);
		if(!b2){
			throw  new RuntimeException("库存改变失败");
		}
	}
	public int reduceQuantity(String stockId,Integer version ,Integer quantity){
	            //  update stock 
				//	set quantity = quantity - #{quantity} ,  version =#{version}+1  
	            //  where id = #{stockId} and version = #{version} and (quantity - #{quantity})>0 
	}
  • 4
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值