购买超卖问题

购买超卖问题

在实际的应用场景中,我们会有优惠券之类的活动,而优惠券的数量是有限的,我们通过查询数据库的余量进行对比,如果还有余量,那么就会进行售卖,没有就停止。但由于多线程,会出现这样一个情况,线程1查询到数据库还有余量1,此时说明线程1还可以进行购买优惠券,但此时突然线程1被叫停了,线程1 的余量-1还没有写回数据库,此时线程2进入查询数据库,发现余量还剩余1,说明还可以进行购买优惠券,所以这两个线程都可以对余量进行减1,导致余量出现负数,这样就出现了超卖问题。

测试

准备数据表

create table order(
	id bigint primary key,
    surplus int
)

创建Controller

@RestController
@RequestMapping("/order")
public class MyOrderController {
    @Resource
    MyOrderService myOrderService;

    @GetMapping("/buy")
    public BaseResponse<String> buyOne() throws InterruptedException {
        MyOrder myOrder = myOrderService.getById(1);
        int count = myOrder.getSurplus();
        if(count > 0){
            myOrderService.update().setSql("surplus = surplus - 1 ").eq("id",1).update();

            return ResultUtils.success("ok");
        }
        throw new BusinessException(ErrorCode.SYSTEM_ERROR,"库存不够");
    }
}

进行测试

利用Jmeter进行大量的请求测试

也就是在8s内进行1000个请求,设置初始数据库中只有300数量,然后最后看数据库的结果

购买超卖问题_第1张图片

购买超卖问题_第2张图片

数据库初始数据截图

购买超卖问题_第3张图片

运行结果截图:

购买超卖问题_第4张图片

这里我有个问题一直弄了很久代码如下

@RestController
@RequestMapping("/order")
public class MyOrderController {
    @Resource
    MyOrderService myOrderService;

    @GetMapping("/buy")
    public BaseResponse<String> buyOne(){
        MyOrder myOrder = myOrderService.getById(1);
        int count = myOrder.getSurplus();
        if(count > 0){
            myOrder.setSurplus(count - 1);
            myOrderService.updateById(myOrder);
            return ResultUtils.success("ok"     );
        }
        throw new BusinessException(ErrorCode.SYSTEM_ERROR,"库存不够");
    }
}

这样进行操作使得数据的数据永远都是不会小于0,但实际进来的线程就会很多,欺骗了自己的眼睛,因为这里去更新数据不是使用最新的数据。而是用的旧数据所以更新永远不会小于0.

解决问题

使用乐观锁

乐观锁就是只在更改数据的时候对操作上锁,然后减少出现异常,使用情况是写入操作少的时候。

两种方法

  • 使用版本号
    • 会有一个版本号,每次操作数据会对版本号+1,再提交回数据时,会去校验是否比之前的版本大1 ,如果大1 ,则进行操作成功,这套机制的核心逻辑在于,如果在操作过程中,版本号只比原来大1 ,那么就意味着操作过程中没有人对他进行过修改,他的操作就是安全的,如果不大1,则数据被修改过
  • cas(compare and set)
    • 利用cas进行无锁化机制加锁,var5 是操作前读取的内存值,while中的var1+var2 是预估值,如果预估值 == 内存值,则代表中间没有被人修改过,此时就将新值去替换 内存值
更新后的代码

这里使用的方法是cas

@RestController
@RequestMapping("/order")
public class MyOrderController {
    @Resource
    MyOrderService myOrderService;

    @GetMapping("/buy")
    public BaseResponse<String> buyOne() throws InterruptedException {
        MyOrder myOrder = myOrderService.getById(1);
        int count = myOrder.getSurplus();
        if(count > 0){
            myOrderService.update().setSql("surplus = surplus - 1 ").eq("id",1).gt("surplus",0).update();

            return ResultUtils.success("ok");
        }
        throw new BusinessException(ErrorCode.SYSTEM_ERROR,"库存不够");
    }
}

这样改后就不会出现超出现象

你可能感兴趣的:(Java,数据库,java,spring,boot)