一、秒杀场景原始代码
秒杀时候,发现代码出现超卖的问题。
最原始控制器代码:
@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,现在已经没有超卖问题了。