使用laravel解决库存超出的几个方案

数据库字段

使用laravel解决库存超出的几个方案

1.错误的示范

    /**
     * 错误示范
     * Create by Peter Yang
     * 2021-06-08 10:57:59
     * @return string
     */
    function test1()
    {

        //商品id
        $id = request()->input('id');

        $product = Product::where('id', $id)->firstOrFail();

        if ($product->num <= 0) {

            return "卖光啦!!";
        }

        //库存减1
        $product->decrement('num');

        return "success";

    }

使用go模拟并发

package main

import (
    "fmt"
    "github.com/PeterYangs/tools/http"
    "sync"
)

func main() {

    client := http.Client()

    wait := sync.WaitGroup{}

    for i := 0; i < 50; i++ {

        wait.Add(1)

        go func(w *sync.WaitGroup) {

            defer w.Done()

            res, _ := client.Request().GetToString("http://www.api/test1?id=1")

            fmt.Println(res)

        }(&wait)

    }

    wait.Wait()

}

在数据库中查看库存

使用laravel解决库存超出的几个方案
库存已超出

2.redis原子锁

    /**
     * redis原子锁
     * Create by Peter Yang
     * 2021-06-08 11:00:31
     */
    function test2()
    {
        //商品id
        $id = request()->input('id');

        $lock = \Cache::lock("product_" . $id, 10);

        try {

            //最多等待5秒,5秒后未获取到锁,则抛出异常
            $lock->block(5);

            $product = Product::where('id', $id)->firstOrFail();

            if ($product->num <= 0) {

                return "卖光啦!!";
            }
            //库存减1
            $product->decrement('num');

            return 'success';

        }catch (LockTimeoutException $e) {

            return '当前人数过多';

        } finally {

            optional($lock)->release();
        }
    }

库存正常

使用laravel解决库存超出的几个方案

3.mysql悲观锁

    /**
     * mysql悲观锁
     * Create by Peter Yang
     * 2021-06-08 11:00:47
     */
    function test3()
    {

        //商品id
        $id = request()->input('id');

        try {
            \DB::beginTransaction();
            $product = Product::where('id', $id)->lockForUpdate()->first();

            if ($product->num <= 0) {

                return "卖光啦!!";
            }

            //库存减1
            $product->decrement('num');

            \DB::commit();

            return "success";

        } catch (\Exception $exception) {

        }

    }

库存正常

使用laravel解决库存超出的几个方案

4.mysql乐观锁

    /**
     * mysql乐观锁
     * Create by Peter Yang
     * 2021-06-08 11:00:47
     */
    function test4()
    {

        //商品id
        $id = request()->input('id');

        $product = Product::where('id', $id)->first();

        if ($product->num <= 0) {

            return "卖光啦!!";
        }

        //修改前检查库存和之前是否一致,不一致说明已经有变动,则放弃更改
        $res = \DB::update('UPDATE `product` SET num = num -1 WHERE id = ? AND num=?', [$id, $product->num]);

        if (!$res) {

            return '当前人数过多';

        }

        return 'success';


    }

库存正常

使用laravel解决库存超出的几个方案

优化乐观锁

修改库存的sql修改为

\DB::update('UPDATE `product` SET num = num -1 WHERE id = ? AND num-1 >= 0', [$id]);

5.redis存储库存

    /**
     * 库存放在redis
     * Create by Peter Yang
     * 2021-06-15 15:18:31
     * @return string
     */
    function test5()
    {

        //商品id
        $id = request()->input('id');


        $num = Redis::command('get', ['product_' . $id]);


        if ($num <= 0) {

            return "卖完啦!";
        }

        //减库存
        $re = Redis::command('decrby', ['product_' . $id, 1]);


        //减多了回滚
        if ($re < 0) {

            Redis::command('incrby', ['product_' . $id, 1]);


            return "卖完啦!";

        }

        return 'success';

    }
本作品采用《CC 协议》,转载必须注明作者和本文链接
本帖由系统于 2年前 自动加精
《L05 电商实战》
从零开发一个电商项目,功能包括电商后台、商品 & SKU 管理、购物车、订单管理、支付宝支付、微信支付、订单退款流程、优惠券等
《G01 Go 实战入门》
从零开始带你一步步开发一个 Go 博客项目,让你在最短的时间内学会使用 Go 进行编码。项目结构很大程度上参考了 Laravel。
讨论数量: 49
Product::query()->where('id', $id)->where('num', '>=', 1)->decrement('num');
2年前 评论
raybon 2年前
williamQian 2年前
raybon 2年前
williamQian 2年前
FM 2年前
drom 2年前
FM 2年前
imzhi 2年前
// UPDATE `product` SET num = num -1 WHERE id = ? AND num-1 >= 0
UPDATE `product` SET num = num -1 WHERE id = ? AND num >= 1
2年前 评论

现在很需要用 大佬

2年前 评论

应该加一个把数量 push 到 redislist 上, 这样子就不用加锁了.

2年前 评论
一位不愿意透露姓名的杨先生 (楼主) 2年前
seth-shi (作者) 2年前
zero风来 2年前
// UPDATE `product` SET num = num -1 WHERE id = ? AND num-1 >= 0
UPDATE `product` SET num = num -1 WHERE id = ? AND num >= 1
2年前 评论

把数据库设置成无符号

2年前 评论
porygonCN 2年前
Itrainee 2年前
osang 11个月前

偏个题,PHP 作为一门图灵完备语言,模拟并发完全可以自己语言内解决,可以看这篇 数据库不使用悲观锁导致问题的一种复现方式

2年前 评论
一位不愿意透露姓名的杨先生 (楼主) 2年前
tu6ge-php (作者) 2年前
seth-shi 2年前
Product::query()->where('id', $id)->where('num', '>=', 1)->decrement('num');
2年前 评论
raybon 2年前
williamQian 2年前
raybon 2年前
williamQian 2年前
FM 2年前
drom 2年前
FM 2年前
imzhi 2年前

如果用悲观锁的话,当并发量大的时候,虽然数据会保证准确性,但是会造成堵塞、响应会变慢,体验不好。

2年前 评论

库存字段无符号unsigned,不就少了个num>0的条件吗

2年前 评论

如果使用乐观锁或者悲观锁,虽然能保证数量正确,但是会有大量的请求返回 当前人数过多 。测试请求数量100009896 个返回 当前人数过多。测试使用的是 memcached 数量没有啥问题,redis 还没有测试。建议如果并发量大的话不要使用悲观锁或者乐观锁。

2年前 评论

目前使用的是5,后台生产商品的库存redis,API消费库存。

2年前 评论

这种秒杀环境还是老老实实的用redis吧,

2年前 评论

redis 原子锁好评 :+1:

2年前 评论

一般来说并发都不能在数据库层面去解决的,redis首选 :smile:

2年前 评论

单机单库存的情况,无符号类型,直接 update t set stock=stock-1 where id=1 就解决了。还搞那么复杂花里胡骚的。

2年前 评论
l333308 2年前
FM (作者) 2年前
l333308 2年前
FM (作者) 2年前

从数据库实现,我这个最牛逼。不接受反驳 function test3()
{

    //商品id
    $id = request()->input('id');

    try {
        \DB::beginTransaction();

        //库存减1
        $product->decrement('num');

        $product = Product::where('id', $id)->first();

        if ($product->num <= 0) {
          throw new Exception("卖光啦!!");
        }



        \DB::commit();

        return "success";

    } catch (\Exception $exception) {
         \DB::rollback();
    }

}
2年前 评论
xiongy 2年前
陈先生 2年前
Horizon 2年前
MrHao (作者) 2年前
kowy 2年前
MrHao (作者) 2年前
lwy2413050238 1年前
MrHao (作者) 1年前

超卖超卖! 是一个很综合的话题,面试高发问题! 总结下思路: 1:redis 锁 2:mysql本身机制 a:读锁 b:写锁

2年前 评论

我都是用redis lua脚本去实现的扣库存和事务

1年前 评论

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