Laravel 悲观锁 并发减库存,出现负数的问题。

try{

 DB::beginTransaction();
      $res = DB::transaction(
      function (){
          $user = User::lockForUpdate() ->first();
          if($user->count < 5){
               return "库存不足以您的订单";
          }
          User::where("id",1) 
          -> decrement("count",5);
  });
  DB::commit();
}
catch(\Exception $ex) {
  DB::rollBack();
  throw $ex;
}

Laravel   悲观锁处理脏数据,无法进行阻塞

第一种情况:Python爬虫请求(使用grequest并发请求)并发测试,一次发送6个请求访问同一个接口:

Laravel   悲观锁处理脏数据,无法进行阻塞

Laravel   悲观锁处理脏数据,无法进行阻塞

第二种情况:手动测试同时刷新两个窗口,添加休眠:

 $user = User::lockForUpdate() ->first();
 sleep(5);

Laravel   悲观锁 并发见库存。

Laravel   悲观锁 并发见库存。

Laravel   悲观锁 并发见库存。

Laravel   悲观锁 并发见库存。

第一个页面加载完成5秒后,第二个页面加载完成。说明进行了阻塞。锁起作用了。

但是请求非常频繁的情况下(第一种情况下)为什么不起作用了?

第二种情况发生了阻塞,没有出现count字段出现负数的情况,是不是因为不同窗口的问题。第一种情况相当于,刷新同一个窗口。

附言 1  ·  2年前

第二种情况开3个窗口 ,依次执行,每次相隔5秒。应该是不能同窗口的问题,多个窗口相当于多个用户一起发送请求,并且进行阻塞说明锁起到了作用。

附言 2  ·  2年前

不使用DB::transaction,也可以进行阻塞

附言 3  ·  2年前

不同的方法(代码一样),但是没有进行阻塞。出现库存为负数的情况。是怎么回事??

sky1121
《L03 构架 API 服务器》
你将学到如 RESTFul 设计风格、PostMan 的使用、OAuth 流程,JWT 概念及使用 和 API 开发相关的进阶知识。
《L02 从零构建论坛系统》
以构建论坛项目 LaraBBS 为线索,展开对 Laravel 框架的全面学习。应用程序架构思路贴近 Laravel 框架的设计哲学。
sky1121
最佳答案

public function text4(){
//乐观锁
DB::beginTransaction();
try{

        $user = User::where("id",1) ->first();
        echo $user -> count;
        // sleep(2);
        $num = rand(1,8);
        if($user->count < $num){
            return  "库存不足以您的订单";
        }
        DB::table("user")->where("id",1) -> decrement("count",$num);

        if(DB::table("user")->where("id",1) -> value("count") < 0){
            DB::rollBack();
            throw new \Exception("库存不足,进行回滚");

        }
        DB::commit();
        return "库存剩余".$user->count."/减去".$num."/";

    }
    catch(\Exception $ex) {
        DB::rollBack();
    }
}

 public function text5(){
    // 悲观锁
     try{
        DB::beginTransaction(); 
        $user = User::lockForUpdate() ->where("id",1) ->first();
         $num = 5;
         if($user->count < $num){
             return  "库存不足以您的订单";
         }
         User::where("id",1) -> decrement("count",$num);
         DB::commit();


         return "库存剩余".$user->count."/减去".$num."/".User::where("id",1) -> value("count");

     }
     catch(\Exception $ex) {
         DB::rollBack();
         return "提交订单失败";
     }
 }

file

问题解决了:数据不能回滚问题,主要是粗心没有改数据库的引擎,按照逻辑执行 都正常,之前的测试2说明事务锁已经生效了进行了阻塞。
在此感谢各位参与 评论、回答、讲解。非常感谢大家

2年前 评论
讨论数量: 7

mysql文档中有这样一句话 A record lock is a lock on an index record
大白话告诉你就是一条sql使用悲观锁,如果没有走索引是不行的。如果你的sql是这样,

select * from users where id = 1 for update

那么在你并发测试中是不会出现负数的,而这行代码$user = User::lockForUpdate() ->first();,解析为sql是这样的,

select * from `users` limit 1 for update;

显然,没有走索引,所以加锁无效。

除此之外,我很久没写laravel,但我感觉你laravel开启事务的姿势重复了吧

2年前 评论
cvoid 2年前
L学习不停 (作者) 2年前
sky1121 (楼主) 2年前
cvoid 2年前
cvoid 2年前

添加休眠,没有怎么出现并发这样子的

2年前 评论

感觉没必要加事物,并发大容易死锁吧。加个乐观锁,where库存>0一类的不就行了,执行失败影响0行就不继续操作,把这个放在第一个执行就不需要开事物了。要开事物就影响0行回滚。

2年前 评论

mysql文档中有这样一句话 A record lock is a lock on an index record
大白话告诉你就是一条sql使用悲观锁,如果没有走索引是不行的。如果你的sql是这样,

select * from users where id = 1 for update

那么在你并发测试中是不会出现负数的,而这行代码$user = User::lockForUpdate() ->first();,解析为sql是这样的,

select * from `users` limit 1 for update;

显然,没有走索引,所以加锁无效。

除此之外,我很久没写laravel,但我感觉你laravel开启事务的姿势重复了吧

2年前 评论
cvoid 2年前
L学习不停 (作者) 2年前
sky1121 (楼主) 2年前
cvoid 2年前
cvoid 2年前
sky1121

@deatil 使用Python做批量发送请求,一次发六个请求。同时访问一个页面。发 50次左右。最后6个请求会 出现 库存负数的问题。

2年前 评论
sky1121

public function text4(){
//乐观锁
DB::beginTransaction();
try{

        $user = User::where("id",1) ->first();
        echo $user -> count;
        // sleep(2);
        $num = rand(1,8);
        if($user->count < $num){
            return  "库存不足以您的订单";
        }
        DB::table("user")->where("id",1) -> decrement("count",$num);

        if(DB::table("user")->where("id",1) -> value("count") < 0){
            DB::rollBack();
            throw new \Exception("库存不足,进行回滚");

        }
        DB::commit();
        return "库存剩余".$user->count."/减去".$num."/";

    }
    catch(\Exception $ex) {
        DB::rollBack();
    }
}

 public function text5(){
    // 悲观锁
     try{
        DB::beginTransaction(); 
        $user = User::lockForUpdate() ->where("id",1) ->first();
         $num = 5;
         if($user->count < $num){
             return  "库存不足以您的订单";
         }
         User::where("id",1) -> decrement("count",$num);
         DB::commit();


         return "库存剩余".$user->count."/减去".$num."/".User::where("id",1) -> value("count");

     }
     catch(\Exception $ex) {
         DB::rollBack();
         return "提交订单失败";
     }
 }

file

问题解决了:数据不能回滚问题,主要是粗心没有改数据库的引擎,按照逻辑执行 都正常,之前的测试2说明事务锁已经生效了进行了阻塞。
在此感谢各位参与 评论、回答、讲解。非常感谢大家

2年前 评论

试试文件锁

$fp = fopen("./lock_goods{$goods_id}.txt", "w+");
if (flock($fp, LOCK_EX)) {
    //..
    if ($no_stock) { 
          flock($fp, LOCK_UN);
          fclose($fp);
          return ['code'=>0, 'msg'=>'库存不足'];
    }
    //正常购买逻辑..
    flock($fp, LOCK_UN);
    fclose($fp);
}
2年前 评论
sky1121 (楼主) 2年前

redis队列 更合适

2年前 评论
sky1121 (楼主) 2年前

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!