解决方案:乐观锁,悲观锁,redis watch,redis分布式锁,消息队列
- 将库存字段number字段设为unsigned,当库存为0时,因为字段不能为负数,将会返回false
乐观锁
//乐观锁,通过版本号实现,数据库中加入一个version,A用户读取version等于1=时,完成减扣之后将version+1
DB::connection("mysql_member")->beginTransaction();
$version = 1; //假设A用户拿到的版本是1,根据实际用户获取到版本号
$goods_id = 1000; //商品id
try {
//判断数据
$result = LotteryPrizeType::query()->where('version', '=', $version)->where('goods_id', '=', $goods_id)->first();
if (!$result) {
throw new ApiException("未找到数据");
}
//先判断库存
$result = LotteryPrizeType::query()->where('goods_id', '=', $goods_id)->first();
if ($result['num'] <= 0) { //判断库存
DB::connection("mysql_member")->rollBack();
throw new ApiException("库存不足");
}
LotteryPrizeType::query()->where('version', '=', $version)->increment("version"); //增加版本号
LotteryPrizeType::query()->where('goods_id', '=', $version)->decrement("num"); //减去库存
DB::connection("mysql_member")->commit();
} catch (\Exception $exception) {
DB::connection("mysql_member")->rollBack();
throw new ApiException($exception->getMessage());
}
悲观锁
//悲观锁,也就是在修改数据的时候,采用锁定状态,排斥外部请求的修改。遇到加锁的状态,就必须等待。
DB::connection("mysql_member")->beginTransaction();
$version = 1; //假设A用户拿到的版本是1,根据实际用户获取到版本号
$goods_id = 1000; //商品id
try {
$result = LotteryPrizeType::query()->where('goods_id', '=', $goods_id)->lockForUpdate()->first();
if ($result['num'] <= 0) { //判断库存
DB::connection("mysql_member")->rollBack();
throw new ApiException("库存不足");
}
LotteryPrizeType::query()->where('goods_id', '=', $version)->decrement("num"); //减去库存
DB::connection("mysql_member")->commit();
} catch (\Exception $exception) {
DB::connection("mysql_member")->rollBack();
throw new ApiException($exception->getMessage());
}
redis的watch
//1.比如我有商品详情页详情接口将库存写入到redis
//2.通过watch从redis判断库存是否剩余
$goodsId = 10000; //商品id
$lottery = LotteryPrizeType::query()->where('goods_id', $goodsId)->first();
if (empty($lottery)) {
throw new ApiException("未找到数据");
}
$key = "goods_id" . "-" . $goodsId;
$redis = Redis::connection();
$redis->select(0); //选择数据库
$number = 10; //库存数据
$n = $this->redis->get($key) ?: 0; //获取redis的库存
if ($n > 0) {
$this->redis->watch($key);
$this->redis->multi();
$this->redis->incr($key);
$exec = $this->redis->exec();
if ($exec) {
//减扣成功
}
}else{
//库存不足
}
redis分布式锁