记数据库更新操作中set和and联合使用的坑

一、秒杀场景原始代码

       秒杀时候,发现代码出现超卖的问题


       最原始控制器代码:

	@RequestMapping("/miaosha")
	@Controller
	public class MiaoshaController {
	    @Autowired
	    private IMiaoshaService iMiaoshaService;
	    @Autowired
	    private IOrderService iOrderService;
	    @Autowired
	    private IGoodService iGoodService;
	
	    @RequestMapping("/do_miaosha")
	    public String list(Model model,
	                       User user,
	                       @RequestParam("goodsId") long goodsId) {
	        /*
	        model.addAttribute("user", user);
	        if (user == null) {
	            return "login";
	        }
	         */
	
	        Long userId = 17367117439L;     //TODO Change
	        userId = user.getId();
	        GoodVO goods = iGoodService.getDetailById(goodsId);
	
	        //判断库存
	        int stock = goods.getStockCount();
	        if (stock <= 0) {
	            model.addAttribute("errmsg", CodeEnum.MIAOSHA_OVER.getMsg());
	            return "miaosha_fail";
	        }
	        //判断是否已经秒杀到了
	        boolean flag = iOrderService.hasMiaoshaOrder(userId, goodsId);
	        if (flag) {
	            model.addAttribute("errmsg", CodeEnum.REPEATE_MIAOSHA.getMsg());
	            return "miaosha_fail";
	        }
	        //减库存 下订单 写入秒杀订单
	        Order order = iMiaoshaService.doMiaosha(userId, goods);
	        model.addAttribute("orderInfo", order);
	        model.addAttribute("goods", goods);
	        return "order_detail";
	    }
	}

       秒杀业务代码:

	@Service("iMiaoshaService")
	public class MiaoshaServiceImpl implements IMiaoshaService {
	    @Autowired
	    private IGoodService iGoodService;
	    @Autowired
	    private IOrderService iOrderService;
	
	    @Transactional
	    @Override
	    public Order doMiaosha(Long userId, GoodVO goodVO) {
	        //减库存
	        iGoodService.reduceStock(goodVO);
	        
	        //下订单
	        return iOrderService.createOrder(userId, goodVO);
	    }
	}

       减库存的mybatis代码如下:

	<update id="reduceCount">
      UPDATE miaosha_goods set stock_count = stock_count - 1
      <where>
          goods_id = #{goodsId} 
      </where>
  </update>

       上述代码就是先判断是否还有库存,如果有库存再判断用户是否已经秒杀成功了,最后才是写入秒杀订单。


       很明显,上述代码存在问题,高并发下面会有大量用户同时涌入,此时商品库存都为正的,但是后面更新库存的时候,直接将商品数量减成了负数,此时就存在超卖问题。




二、秒杀场景修改1

       首先利用数据库的行锁,修改SQL语句如下:

	<update id="reduceCount">
      UPDATE miaosha_goods set stock_count = stock_count - 1  AND stock_count > 0
      <where>
          goods_id = #{goodsId}
      </where>
  </update>

       没错,最开始的代码是这样的!后来直接执行一次SQL语句,看了下执行结果。发现库存直接变为了1!!!不管原来的库存为多少,只要大于0,都会变为1。

       经过分析,其实set语句后面要想更新多个属性的话,是直接使用”逗号“进行分隔的,要是使用了AND,则会根据右侧的结果进行与运算。。。


       真正可用的SQL语句如下:

	<update id="reduceCount">
      UPDATE miaosha_goods set stock_count = stock_count - 1
      <where>
          goods_id = #{goodsId} AND stock_count > 0
      </where>
  </update>

       仅仅这么改是远远不够的,此时如果库存未更新即当前库存已为零的时候,还会生成新的订单,也是超卖问题。




三、秒杀场景修改2

       利用事务回滚的特性,解决超卖问题。

       修改秒杀业务的代码为:

	@Service("iMiaoshaService")
	public class MiaoshaServiceImpl implements IMiaoshaService {
	    @Autowired
	    private IGoodService iGoodService;
	    @Autowired
	    private IOrderService iOrderService;
	
	    @Transactional
	    @Override
	    public Order doMiaosha(Long userId, GoodVO goodVO) {
	        //减库存
	        int count = iGoodService.reduceStock(goodVO);
	        if (count < 1) {
	            throw new GlobalException(CodeEnum.MIAOSHA_OVER);
	        }
	        //下订单
	        return iOrderService.createOrder(userId, goodVO);
	    }
	}

       OK,现在已经没有超卖问题了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值