购买超卖问题
在实际的应用场景中,我们会有优惠券之类的活动,而优惠券的数量是有限的,我们通过查询数据库的余量进行对比,如果还有余量,那么就会进行售卖,没有就停止。但由于多线程,会出现这样一个情况,线程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数量,然后最后看数据库的结果
数据库初始数据截图
运行结果截图:
这里我有个问题一直弄了很久代码如下
@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,"库存不够");
}
}
这样改后就不会出现超出现象