int stock = mapper.getStockById(123);
if(stock > 0) {
int count = mapper.updateStock(123);
if(count > 0) {
addOrder(123);
}
}
大家有没有发现这段代码的问题?
没错,查询操作和更新操作不是原子性的,会导致在并发的场景下,出现库存超卖的情况。
有人可能会说,这样好办,加把锁,不就搞定了,比如使用synchronized关键字。
确实,可以,但是性能不够好。
还有更优雅的处理方案,即基于数据库的乐观锁,这样会少一次数据库查询,而且能够天然的保证数据操作的原子性。
只需将上面的sql稍微调整一下:
update product set stock=stock-1 where id=product and stock > 0;
在sql最后加上:stock > 0,就能保证不会出现超卖的情况。
但需要频繁访问数据库,我们都知道数据库连接是非常昂贵的资源。在高并发的场景下,可能会造成系统雪崩。而且,容易出现多个请求,同时竞争行锁的情况,造成相互等待,从而出现死锁的问题。
6.2 redis扣减库存redis的incr方法是原子性的,可以用该方法扣减库存。伪代码如下:
boolean exist = redisClient.query(productId,userId);
if(exist) {
return -1;
}
int stock = redisClient.queryStock(productId);
if(stock <=0) {
return 0;
}
redisClient.incrby(productId, -1);
redisClient.add(productId,userId);
return 1;
代码流程如下:
1. 先判断该用户有没有秒杀过该商品,如果已经秒杀过,则直接返回-1。
2. 查询库存,如果库存小于等于0,则直接返回0,表示库存不足。
3. 如果库存充足,则扣减库存,然后将本次秒杀记录保存起来。然后返回1,表示成功。
估计很多小伙伴,一开始都会按这样的思路写代码。但如果仔细想想会发现,这段代码有问题。
有什么问题呢?