奥丁9
奥丁9
后端
数据库
redis
mysql
mongoDB
达梦
php
laravel
laravel-admin
dcat
表单
表格
java
spring
python
go
c
c++
前端
vue
nodejs
sass/less
html/css
前端框架
javascript
微信生态
公众号
小程序
uniapp
typescript
其他
AI
数据结构
安全
linux
seo
git
健身
算法
正则表达式
docker
待分类
后端
/
php
/
laravel
laravel-shop电商实战:防止订单超卖
1年前
aoding9
40
php
laravel
电商系统
mysql
下单减库存,\DB::transaction开启事务在回调中如果抛异常会回滚 为什么不在update里面更新库存,而是单独执行一个sql: 因为innodb默认可重复读,事务里面虽然锁了,但是高并发时可能读出相同的库存数,用这个减库存,第一次1-1=0,库存为0了,第二次还是1-1=0,这样就超卖了。 而decrement的sql是类似`set stock = stock - $amount`,这个stock是锁里用执行sql时的库存去减。 ```php public function store(OrderRequest $request) { $user = $request->user(); // 开启一个数据库事务 $order = \DB::transaction(function () use ($user, $request) { $address = UserAddress::find($request->input('address_id')); // 更新此地址的最后使用时间 $address->update(['last_used_at' => Carbon::now()]); // 创建一个订单 $order = new Order([ 'address' => [ // 将地址信息放入订单中 'address' => $address->full_address, 'zip' => $address->zip, 'contact_name' => $address->contact_name, 'contact_phone' => $address->contact_phone, ], 'remark' => $request->input('remark'), 'total_amount' => 0, ]); // 订单关联到当前用户 $order->user()->associate($user); // 写入数据库 $order->save(); $totalAmount = 0; $items = $request->input('items'); // 遍历用户提交的 SKU foreach ($items as $data) { $sku = ProductSku::find($data['sku_id']); // 创建一个 OrderItem 并直接与当前订单关联 $item = $order->items()->make([ 'amount' => $data['amount'], 'price' => $sku->price, ]); $item->product()->associate($sku->product_id); $item->productSku()->associate($sku); $item->save(); $totalAmount += $sku->price * $data['amount']; // decreaseStock返回受影响的行数,据此判断是否修改成功 if ($sku->decreaseStock($data['amount']) <= 0) { throw new InvalidRequestException('该商品库存不足'); } } // 更新订单总金额 $order->update(['total_amount' => $totalAmount]); // 将下单的商品从购物车中移除 $skuIds = collect($items)->pluck('sku_id'); $user->cartItems()->whereIn('product_sku_id', $skuIds)->delete(); // 触发关闭订单延迟任务 $this->dispatch(new CloseOrder($order, config('app.order_ttl'))); return $order; }); return $order; } ``` ```php <?php namespace App\Models; use App\Exceptions\InternalException; use Illuminate\Database\Eloquent\Model; class ProductSku extends Model { /*** decreaseStock() 方法里我们用了 decrement() 方法来减少字段的值,该方法会返回影响的行数。 最终执行的 SQL 类似于 update product_skus set stock = stock - $amount where id = $id and stock >= $amount,这样可以保证不会出现执行之后 stock 值为负数的情况,也就避免了超卖的问题。而且我们可以通过检查 decrement() 方法返回的影响行数来判断减库存操作是否成功,如果不成功说明商品库存不足。 addStock() 加库存的逻辑里面不需要像减库存那样判断了,但仍需通过 increment() 方法来保证操作的原子性。 */ public function decreaseStock($amount) { if ($amount < 0) { throw new InternalException('减库存不可小于0'); } // decrement会生成set stock = stock - $amount 。stock是事务提交执行时才查的,并发需要等上一个事务执行完,释放锁之后再查,可以防止超卖,如果在php里用$stock - $amount,由于innodb默认可重复读,同时有两个相同的$stock查出来,第二个就可能超卖。 // 加了where('stock', '>=', $amount) 根据decrement返回影响的行数,<=0说明修改失败,如果不加,sql执行报错回滚 return $this->where('id', $this->id)->where('stock', '>=', $amount)->decrement('stock', $amount); } public function addStock($amount) { if ($amount < 0) { throw new InternalException('加库存不可小于0'); } // increment保证操作原子性 $this->increment('stock', $amount); } } ```
本作品采用
《CC 协议》
,转载必须注明作者和本文链接