解决超卖问题
- 一. 在SQL加上判断库存防止库存为负数
- 二. 数据库加唯一索引防止用户重复购买
- 三. Redis预减库存减少数据库访问,内存标记减少Redis访问
- 三. 悲观锁 加同步代码块 效率低
- 四. 乐观锁 Version版本 效率高
一. 在SQL加上判断库存防止库存为负数
可以简单的解决超卖的情况,但不能完全避免
public interface MiaoshaGoodsMapper extends Mapper<MiaoshaGoods> {
//更新数据库中减库存1 并且stock_count>0
@Update("update miaosha_goods set stock_count=stock_count-1 where goods_id=#{goodsId} and stock_count>0")
public int reduceStockById(@Param("goodsId") Long goodsId);
}
二. 数据库加唯一索引防止用户重复购买
三. Redis预减库存减少数据库访问,内存标记减少Redis访问
- 利用redis的单线程预减库存。比如商品有100件。那么我在Redis存储一个K,V。例如 <gs1001, 100>,每一个用户线程进来,key值就减1,等减到0的时候,全部拒绝剩下的请求。那么也就是只有100个线程会进入到后续操作。所以一定不会出现超卖的现象
// 商品结束 秒杀标记 map
private Map<Long, Boolean> localOverMap = new HashMap<>();
// ④.预减库存 redis库存减一,返回剩余库存
Long stock = redisService.decr(GoodsPrefix.goodsMiaoshaStock, "" + goodsId);
if (stock < 0) {
//剩余库存秒杀光了 将标记设置为true
localOverMap.put(goodsId, true);
//秒杀结束
return ResultUtil.error(ResultEnum.MIAOSHA_OVER);
}
三. 悲观锁 加同步代码块 效率低
四. 乐观锁 Version版本 效率高
- 这种方式采用了版本号的方式,其实也就是CAS的原理。
- 悲观锁虽然可以解决超卖问题,但是加锁的时间可能会很长,会长时间的限制其他用户的访问,导致很多请求等待锁,卡死在这里,如果这种请求很多就会耗尽连接,系统出现异常。乐观锁默认不加锁,更失败就直接返回抢购失败,可以承受较高并发
@Override
public int createOptimisticOrder(int sid) throws Exception {
// 校验库存
Stock stock = checkStock(sid);
// 乐观锁更新
saleStockOptimstic(stock);
// 创建订单
int id = createOrder(stock);
return id;
}
// 乐观锁 Mapper 文件
@Update("UPDATE stock SET count = count - 1, sale = sale + 1, version = version + 1 WHERE " +
"id = #{id, jdbcType = INTEGER} AND version = #{version, jdbcType = INTEGER}")