@Autowired
private JdbcTemplate jdbcTemplate;
@RequestMapping(value = "/add",method = RequestMethod.GET)
public String request(){
Integer stock;
String sql = "select stock from shop_order where id=1 ";
List<Map<String, Object>> result = jdbcTemplate.queryForList(sql);
if (result==null||(stock = (Integer)result.get(0).get("stock"))<=0) {
// log.info("xia dan shibai,meiyou kucun le");
return "shouqing";
}
stock--;
jdbcTemplate.update("update shop_order set stock=? where id=1",stock);
// log.info("shengyu"+stock);
System.out.println("shengyu" + stock);
return "success";
}
听到有老师讲,单机tomcat这段代码会出现高并发访问问题,会出现超卖的行为。
但是我测试了很多遍,包括压测是不会出现超卖的行为的。按我自己的理解,tomcat是以队列的方式一条一条处理请求的,代码走完了,下个线程才会执行,z怎么会出现超卖的行为呢?有知道的大神帮忙解答一下么?
Tomcat 默认配置的最大请求数是 150,也就是说同时支持 150 个并发 ;tomcat8最大并发数10000 ,具体可以百度。
多个请求同时访问到 你这个add接口 ,那么查询库存的操作,可能同一时刻多个请求都会查出来还有库存,导致stock--,出现超卖。
建议 查询库存、更新库存 增加 代码块锁 。代价就是 性能可能会低一些。
如果更新值是在代码曾查出来经过业务处理后再更新数据库则高并发场景会存在并发修改提交的隐患。因为假设在你查出来stock的值之后用一个变量存储stock,并对stock进行运算。如果在此期间有其他请求线程对你刚查询的记录行进行了修改,改动了stock值。这时你这个stock基值就是一个过期值。基于这个值做的所有后续操作都是错误的。
如果你的sql改写成:update shop_order set stock=stock-1 where id=1 and stock-1>0 将不会出现问题。因为该sql stock引用了数据库记录的实时真实数据,数据库默认删除和修改是加锁的。同一时刻只会有一个线程可以对同一记录行做修改。这便保证了高并发下的修改安全。(不过注意,条件判断要控制好。)
你这个代码会出现,假设只有10库存,而你这段代码逻辑却可以生成10+n个订单。理论应该最多只能生成10个订单。
举个例子:A,B同时抢库存。stock初始值=10。
1.A查询出来stock=10,然后执行stock--,stock=9 ,此时A还未提交update操作;
2.B查询出来stock=10,然后执行stock-- ,stock=9 ;
3.A提交update,数据库stock变为9,A增加一条订单记录
4.B提交update,数据库stock还是9,B增加一条订单记录
5.此时只消费了一条库存,却为两个用户生成了两个订单。而现实中可能你只有10个商品,这时候这10个发完。还有这个商品的其他订单,店家怎么办?硬着头皮亏本补货或赔偿用户喽。在极端高并发场景,可能一次操作中途就穿插了多次交易记录。超卖现象甚至会成倍增加。
很危险!