当用户想要购买订单时,应该减少产品的数量。现在我计划使用交易和FOR UPDATE
产品锁定并减少每种产品的数量。
Laravel 伪代码:
// Check quantities before entering the transaction
...
DB::transaction(function () use ($cartItems) {
$ids = $cartItems->pluck('product_id')->toArray(); // Extract product_ids -> [50, 100, 99, ...]
$products = Product::whereIn('id', $ids)->lockForUpdate()->get();
// Check quantities
foreach ($cartItems as $item) {
// Find the associated product
$product = $products->first(fn ($product) => $product->id === $item->product_id);
// Throw an exception if the product quantity is less than the quantity of the cart item
if ($product->quantity < $item->quantity) {
throw new \Exception("Quantity error for Product #{$product->id}");
}
}
// Decrease quantities for each product
foreach ($cartItems as $item) {
$product = $products->first(fn ($product) => $product->id === $item->product_id);
// SET `quantity` = `quantity` - ?
$product->decrement('quantity', $item->quantity);
$product->save();
}
...
});
...
// Go to paypal
为什么要FOR UPDATE
锁?
由于竞争条件。多个用户可以同时订购同一种商品,且商品数量为1。
我尝试记录 Woocommerce 和其他一些电子商务项目的 sql 查询,但它们不使用事务或锁for update
。我还要求 ChatGPT 生成代码,它给了我一段与我的代码类似的代码。
如果我错了,请纠正我和我的代码。
更新
其中一条评论的更新。
在下面的代码中我们有 3 条路线。
/lock-and-sleep
选择带锁的产品for update
,然后休眠 20 秒。/test-without-lock
选择不带锁的相同产品。(独立的)/test-with-lock
选择带 的产品for update
。这取决于第一条路线。
// Response time: 20s~
Route::get('/lock-and-sleep', function () {
DB::transaction(function () {
Product::where('id', 5)->lockForUpdate()->first();
sleep(20);
});
});
// Response time: 0.XYZms (Not Blocked)
Route::get('/test-without-lock', function () {
Product::where('id', 5)->first();
DB::transaction(function () {
Product::where('id', 5)->first();
});
});
// Response time: 20s~ (Blocked)
Route::get('/test-with-lock', function () {
DB::transaction(function () {
Product::where('id', 5)->lockForUpdate()->first();
});
});
在进入交易之前检查数量很少,这会受到竞争条件的影响,唯一有意义的检查是在锁定下的交易中进行的检查
forUpdate
。您可以将其
decrement
与支票放在同一循环中。DB::transaction
处理事务的开始、退出时提交和异常时回滚。应该有代码可以将订单保存在表中,对吧?这应该在同一笔交易中。
循环之后(并保存顺序)应该有一个显式的
commit
.(可选)为了用户体验,计算所有商品的数量是否正确,如果多个产品的数量不足,则将它们汇总到同一错误消息中。
在循环外处理 paypay 意味着订单取消是一个单独的过程。