› MySQL 5.5 Community Server
› MySQL 5.6 Community Server
› Percona Configuration Wizard
› XtraBackup 搭建主从复制
Great Sites on MySQL
› Percona
› MySQL Performance Blog
› Severalnines
推荐管理工具
› Sequel Pro
› phpMyAdmin
推荐书目
› MySQL Cookbook
MySQL 相关项目
› MariaDB
› Drizzle
参考文档
› http://mysql-python.sourceforge.net/MySQLdb.html
chwangtenger
V2EX  ›  MySQL

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

  •  
  •   chwangtenger · Jul 10, 2020 · 4267 views
    This topic created in 2307 days ago, the information mentioned may be changed or developed.

    最近从 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 replies  •  2020-07-11 21:25:46 +08:00
    Maboroshii
    Maboroshii
    Jul 10, 2020
    1
    事务的隔离级别?
    2kCS5c0b0ITXE5k2
    2kCS5c0b0ITXE5k2
    Jul 10, 2020
    2
    上个锁吧 读的是脏数据 并发高肯定会超卖的
    limuyan44
    limuyan44
    Jul 10, 2020
    3
    单就 sql 来说没什么问题,这个也不是通过 sql 抛异常事务回滚来控制的,是看 sql 的返回结果是否有更新,没有更新说明无库存来做相应的处理。
    DonaldY
    DonaldY
    Jul 10, 2020
    4
    Affected rows: 0,不就是没有更新行数,不满足条件。

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

    update 会锁住行
    AngryPanda
    AngryPanda
    Jul 10, 2020
    5
    if (affectedRows > 0) {
    throw exception;
    }
    pushback
    pushback
    Jul 10, 2020
    6
    根据 update 返回结果影响行数选择是否回滚
    AngryPanda
    AngryPanda
    Jul 10, 2020
    7
    if (affectedRows <= 0) {
    throw exception;
    }
    chwangtenger
    chwangtenger
    OP
    Jul 10, 2020
    8
    @limuyan44 对,sql 没问题的,但是影响的行是 0 行,也不会报出异常,所以后面的程序代码里顶多只能自己判断返回的影响行数来判断刚才的减库存有没有成功,但是他代码里也没这么写,我感觉老师不是讲错了。但是我又无法解释为啥他并发的结果是对的,一件都没超。
    wysnylc
    wysnylc
    Jul 10, 2020
    9
    数据库解决并发就是扯淡,要么用队列要么 redis incry 要么分布式锁
    AngryPanda
    AngryPanda
    Jul 10, 2020
    10
    @wysnylc 并发量不十分高的时候,用用还是很香的
    limuyan44
    limuyan44
    Jul 10, 2020
    11
    而且你这是个收费课程,连代码都看不到,至少贴段代码出来,你的问题只有上帝知道为什么了。
    wysnylc
    wysnylc
    Jul 10, 2020
    12
    @AngryPanda #10 本地搭个 redis 很快,并发问题不在于量大量小而是并发超卖怎么处理超卖 1 个和 100 个没有区别,老板一样会骂你
    AngryPanda
    AngryPanda
    Jul 10, 2020
    13
    @wysnylc 当并发数并没有大到可以影响数据库的读写性能,我为啥要改代码?

    稍微调整一下 SQL 就解决不可以么?所以我觉得还是得从实际出发。
    kanepan19
    kanepan19
    Jul 10, 2020
    14
    更新是原子的, 但是你 读取库存的时候,不是原子的.
    出库的 manager 启一个事务
    最后更新如果库存操作 如果失败,抛异常回滚.
    则出货失败.
    kanepan19
    kanepan19
    Jul 10, 2020
    15
    更正, 以上读取库存的时候肯能是脏数据.
    chwangtenger
    chwangtenger
    OP
    Jul 10, 2020
    16
    @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
    limuyan44
    Jul 10, 2020
    17
    从代码来看,出现你提问里的情况是正常的现象,对于 createorder 的限制只是在入口的 stock<=0,并发下必然会出现库存为 0 但是订单多了的情况。不过,我看这课程是付费的而且学习人数也不少应该不至于出现这种常识性的错误,不知道是不是老师的最终代码,按理说正常上课会一点一点带着学员修改到正常逻辑的代码。
    cxshun
    cxshun
    Jul 10, 2020
    18
    其实最主要的问题应该是 reduceStock 没有判断返回的条数吧,如果判断一下更新条数再执行后面的创建订单就没问题了。但这种建议还是直接上悲观锁吧,依赖数据库的乐观锁总是不现实的,如果并发量一大,抗不住的。
    xiangyuecn
    xiangyuecn
    Jul 10, 2020
    19
    select xx from xx for update
    sagaxu
    sagaxu
    Jul 11, 2020 via Android
    20
    所有付费的编程网课都是智商税
    chwangtenger
    chwangtenger
    OP
    Jul 11, 2020
    21
    @limuyan44 这已经是最终的代码了。。。
    chwangtenger
    chwangtenger
    OP
    Jul 11, 2020
    22
    不好意思啊各位,后面一章里面说到之前讲错了,后来改成了判断一下影响的行数。
    About   ·   Help   ·   Advertise   ·   Blog   ·   API   ·   FAQ   ·   Privacy   ·   Solana   ·   5297 Online   Highest 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 51ms · UTC 01:27 · PVG 09:27 · LAX 17:27 · JFK 20:27
    ♥ Do have faith in what you're doing.