跳至主要內容
  • Hostloc 空間訪問刷分
  • 售賣場
  • 廣告位
  • 賣站?

4563博客

全新的繁體中文 WordPress 網站
  • 首頁
  • mysql update 库存解决并发卖超的问题失败了 where and 真的可行吗?
未分類
16 7 月 2020

mysql update 库存解决并发卖超的问题失败了 where and 真的可行吗?

mysql update 库存解决并发卖超的问题失败了 where and 真的可行吗?

資深大佬 : chwangtenger 11

最近从 cv 转 java 看一个慕课的教程 《 Java 秒杀系统方案优化 高性能高并发实战》

里面的老师为了解决卖超的问题,用了update miaosha_goods set stock_count = stock_count - 1 where goods_id = #{goodsId} and stock_count > 0这样一句话,他说用and stock_count > 0就可以解决库存变负数的问题,从而解决卖超。因为这一操作和后面生成订单的代码在同一个事务的 annotation 里。

可是我实际跑他一样的代码发现订单还是会卖超,。他视频里演示的时候库存设置 10,最终变为 0,订单也刚好 10 个。但我这里虽然库存变成 0 了,订单有 50 多个。

我在数据库试了下是因为执行这句话的时候数据库没有报异常,所以很多线程继续执行了后续的下订单的流程(虽然减库存和下订单在一个事务里,但是那个 where 并没有触发什么错误,where 条件不满足的地方,就 Affected rows: 0,最终没有出发异常和回滚)我解释不了为什么老师测试的结果是对的,我们都是 1000 个线程,他运气没有那么好吧。

另外我还在网上看到了一些回答也是这种类似的方法,用 where and 。。。这不是条件更新吗?就算不满足也不会抛出异常呀,那就不会自动回滚呀。

因为那个课程 2017 年的,我就下了个 mysql5.6 试试,结果也是一样的,有大侠知道为什么吗。

下面是另外一些提到类似这个方法的 https://blog.csdn.net/qq_16504067/article/details/79485443 https://www.v2ex.com/t/254887

大佬有話說 (22)

  • 資深大佬 : Maboroshii

    事务的隔离级别?

  • 資深大佬 : emeab

    上个锁吧 读的是脏数据 并发高肯定会超卖的

  • 資深大佬 : limuyan44

    单就 sql 来说没什么问题,这个也不是通过 sql 抛异常事务回滚来控制的,是看 sql 的返回结果是否有更新,没有更新说明无库存来做相应的处理。

  • 資深大佬 : DonaldY

    Affected rows: 0,不就是没有更新行数,不满足条件。

    异常是要自己抛的,然后才回滚。

    update 会锁住行

  • 資深大佬 : AngryPanda

    if (affectedRows > 0) {
    throw exception;
    }

  • 資深大佬 : pushback

    根据 update 返回结果影响行数选择是否回滚

  • 資深大佬 : AngryPanda

    if (affectedRows <= 0) {
    throw exception;
    }

  • 主 資深大佬 : chwangtenger

    @limuyan44 对,sql 没问题的,但是影响的行是 0 行,也不会报出异常,所以后面的程序代码里顶多只能自己判断返回的影响行数来判断刚才的减库存有没有成功,但是他代码里也没这么写,我感觉老师不是讲错了。但是我又无法解释为啥他并发的结果是对的,一件都没超。

  • 資深大佬 : wysnylc

    数据库解决并发就是扯淡,要么用队列要么 redis incry 要么分布式锁

  • 資深大佬 : AngryPanda

    @wysnylc 并发量不十分高的时候,用用还是很香的

  • 資深大佬 : limuyan44

    而且你这是个收费课程,连代码都看不到,至少贴段代码出来,你的问题只有上帝知道为什么了。

  • 資深大佬 : wysnylc

    @AngryPanda #10 本地搭个 redis 很快,并发问题不在于量大量小而是并发超卖怎么处理超卖 1 个和 100 个没有区别,老板一样会骂你

  • 資深大佬 : AngryPanda

    @wysnylc 当并发数并没有大到可以影响数据库的读写性能,我为啥要改代码?

    稍微调整一下 SQL 就解决不可以么?所以我觉得还是得从实际出发。

  • 資深大佬 : kanepan19

    更新是原子的, 但是你 读取库存的时候,不是原子的.
    出库的 manager 启一个事务
    最后更新如果库存操作 如果失败,抛异常回滚.
    则出货失败.

  • 資深大佬 : kanepan19

    更正, 以上读取库存的时候肯能是脏数据.

  • 主 資深大佬 : chwangtenger

    @limuyan44 代码有好几层,那我贴一下

    MiaoshaController.java

    @RequestMapping(value=”/do_miaosha”, method= RequestMethod.POST)
    @ResponseBody
    public Result<OrderInfo> miaosha(Model model,MiaoshaUser user,
    @RequestParam(“goodsId”)long goodsId) {
    model.addAttribute(“user”, user);
    if(user == null) {
    return Result.error(CodeMsg.SESSION_ERROR);
    }
    //判断库存
    GoodsVo goods = goodsService.getGoodsVoByGoodsId(goodsId);//10 个商品,req1 req2
    int stock = goods.getStockCount();
    if(stock <= 0) {
    return Result.error(CodeMsg.MIAO_SHA_OVER);
    }
    //判断是否已经秒杀到了
    MiaoshaOrder order = orderService.getMiaoshaOrderByUserIdGoodsId(user.getId(), goodsId);
    if(order != null) {
    return Result.error(CodeMsg.REPEATE_MIAOSHA);
    }
    //减库存 下订单 写入秒杀订单
    OrderInfo orderInfo = miaoshaService.miaosha(user, goods); //我提问的操作在这个函数里面
    return Result.success(orderInfo);
    }

    MiaoshaService.java 中,上文的 miaoshaService.miaosha

    @Transactional
    public OrderInfo miaosha(MiaoshaUser user, GoodsVo goods) {
    //减库存 下订单 写入秒杀订单
    goodsService.reduceStock(goods);
    //order_info maiosha_order
    return orderService.createOrder(user, goods);
    }

    GoodsService.java 中,上文的 goodsService.reduceStock

    public void reduceStock(GoodsVo goods) {
    MiaoshaGoods g = new MiaoshaGoods();
    g.setGoodsId(goods.getId());
    goodsDao.reduceStock(g);
    }

    GoodsDao.java 中,上文的 goodsDao.reduceStock
    @Update(“update miaosha_goods set stock_count = stock_count – 1 where goods_id = #{goodsId} and stock_count > 0”)
    public int reduceStock(MiaoshaGoods g);

    OrderService 上上上段代码最后一句,中的 orderService.createOrder

    @Transactional
    public OrderInfo createOrder(MiaoshaUser user, GoodsVo goods) {
    OrderInfo orderInfo = new OrderInfo();
    orderInfo.setCreateDate(new Date());
    orderInfo.setDeliveryAddrId(0L);
    orderInfo.setGoodsCount(1);
    orderInfo.setGoodsId(goods.getId());
    orderInfo.setGoodsName(goods.getGoodsName());
    orderInfo.setGoodsPrice(goods.getMiaoshaPrice());
    orderInfo.setOrderChannel(1);
    orderInfo.setStatus(0);
    orderInfo.setUserId(user.getId());
    long orderId = orderDao.insert(orderInfo);

    System.out.println();
    MiaoshaOrder miaoshaOrder = new MiaoshaOrder();
    miaoshaOrder.setGoodsId(goods.getId());
    miaoshaOrder.setOrderId(orderId);
    miaoshaOrder.setUserId(user.getId());
    orderDao.insertMiaoshaOrder(miaoshaOrder);

    redisService.set(OrderKey.getMiaoshaOrderByUidGid, “”+user.getId()+”_”+goods.getId(), miaoshaOrder);

    return orderInfo;
    }

    我觉得这两句话虽然在一个事务里,但是第一句里用了 where,虽然库存已经没了,但是没有报异常,导致 return 那句的订单还是可以生成。
    //减库存 下订单 写入秒杀订单
    goodsService.reduceStock(goods);
    //order_info maiosha_order
    return orderService.createOrder(user, goods);

  • 資深大佬 : limuyan44

    从代码来看,出现你提问里的情况是正常的现象,对于 createorder 的限制只是在入口的 stock<=0,并发下必然会出现库存为 0 但是订单多了的情况。不过,我看这课程是付费的而且学习人数也不少应该不至于出现这种常识性的错误,不知道是不是老师的最终代码,按理说正常上课会一点一点带着学员修改到正常逻辑的代码。

  • 資深大佬 : cxshun

    其实最主要的问题应该是 reduceStock 没有判断返回的条数吧,如果判断一下更新条数再执行后面的创建订单就没问题了。但这种建议还是直接上悲观锁吧,依赖数据库的乐观锁总是不现实的,如果并发量一大,抗不住的。

  • 資深大佬 : xiangyuecn

    select xx from xx for update

  • 資深大佬 : sagaxu

    所有付费的编程网课都是智商税

  • 主 資深大佬 : chwangtenger

    @limuyan44 这已经是最终的代码了。。。

  • 主 資深大佬 : chwangtenger

    不好意思啊各位,后面一章里面说到之前讲错了,后来改成了判断一下影响的行数。

文章導覽

上一篇文章
下一篇文章

AD

其他操作

  • 登入
  • 訂閱網站內容的資訊提供
  • 訂閱留言的資訊提供
  • WordPress.org 台灣繁體中文

51la

4563博客

全新的繁體中文 WordPress 網站
返回頂端
本站採用 WordPress 建置 | 佈景主題採用 GretaThemes 所設計的 Memory
4563博客
  • Hostloc 空間訪問刷分
  • 售賣場
  • 廣告位
  • 賣站?
在這裡新增小工具