Laravel 10.43.0:避免并发请求更新同一条数据行
解决Laravel库存并发分配的问题
你的代码确实存在并发安全问题:first()和update()是两个独立的数据库操作,当多个请求同时进来时,它们可能会同时查到同一条available=true的库存记录,进而导致重复分配。
方案一:使用悲观锁(lockForUpdate)
Laravel的悲观锁需要在事务中使用才能生效,它会锁定选中的行,直到当前事务完成,其他请求必须等待锁释放才能操作该行。修改后的代码如下:
public function assignItemToAccount(Account $account): Inventory { return DB::transaction(function () use ($account) { // 锁定第一条可用库存,其他请求需等待当前事务结束 $item = Inventory::where('available', true) ->lockForUpdate() ->first(); if (!$item) { throw new \Exception('暂无可用库存'); } $item->update([ 'account_id' => $account->id, 'available' => false ]); return $item->fresh(); }); }
原理:lockForUpdate()会给查询到的行加上排他锁,在当前事务提交或回滚前,其他请求查询这条记录时会被阻塞,从根本上避免了并发抢占。
方案二:原子更新(更高效)
如果你的业务逻辑只是“选一条可用库存并更新”,推荐用原子更新的方式——把查询和更新合并成一个数据库操作,数据库本身会保证这个操作的原子性,不需要额外加锁,性能更优:
public function assignItemToAccount(Account $account): ?Inventory { // 原子操作:找到第一条可用库存并直接更新,返回受影响的行数 $updatedRows = Inventory::where('available', true) ->orderBy('id') // 固定选取顺序,避免随机分配 ->limit(1) ->update([ 'account_id' => $account->id, 'available' => false ]); if ($updatedRows === 0) { throw new \Exception('暂无可用库存'); } // 获取刚更新的库存记录 return Inventory::where('account_id', $account->id) ->where('available', false) ->orderBy('updated_at', 'desc') ->first(); }
原理:update()是单个数据库请求,数据库会确保同一时间只有一个请求能更新同一条记录,完全避免了并发冲突,适合高并发场景。
方案对比
- 悲观锁:适合需要先读取记录、再执行复杂业务逻辑(比如计算、调用其他接口)后再更新的场景,但会带来一定的性能开销(阻塞等待)。
- 原子更新:适合简单的“选更合一”场景,性能更高,无阻塞。
内容的提问来源于stack exchange,提问作者Vince
相关产品推荐
相关产品推荐

