快捷搜索: 王者荣耀 脱发

秒杀超卖问题解剖及解决方案

超卖问题

秒杀往往伴随着高并发,一个处理不好就会出现超卖问题

问题:先校验产品库存,再更新库存

线程1先校验库存,余100,在线程1未来得及更新库存时,线程2进来校验库存,还是余100,然后两个线程都能更新库存,导致最终结果超卖

解决方案一:乐观锁版本号模式

在更新产品库存时,产品库存等于校验时库存时才更新。 线程1:在校验时库存余100,update t set stock = stock - 60 where id = ? and stock = 100 线程2:在校验时库存余100,update t set stock = stock - 70 where id = ? and stock = 100 线程3:在校验时库存余100,update t set stock = stock - 30 where id = ? and stock = 100

由于在更新库存时有行锁,不同能会同时更新两条,其中一条会阻塞等待另一条更新完,当线程1更新完后库存余40,到线程2再更新时库存已经不是100了,所以线程2会更新失败,这样就可以解决了超卖问题;

再到线程3更新时,线程1更新完库存余40,线程3扣减30,库存充足,理应更新成功,但是由于线程3更新时库存得是100,导致线程3更新失败了;

版本号模式虽然会解决超卖问题,但会导致大量不该失败的请求失败了;

解决方案二:乐观锁,更新后库存大于0

只需要更新后的库存还是大于0就算成功,此时都可以不用先校验库存了 线程1:update t set stock = stock - 60 where id = ? and stock - 60 >= 0 线程2:update t set stock = stock - 70 where id = ? and stock - 70 >= 0 线程3:update t set stock = stock - 30 where id = ? and stock - 30 >= 0

这样不管是线程1先更新,还是线程2先更新,线程3都能够成功更新

问题:为什么不使用悲观锁来解决?

或许会有这种想法:使用悲观锁(java锁),就算先校验库存再更新库存,由于校验库存和更新库存是原子性的,所以也是可以解决的,就算是做集群,也还是可以使用分布式锁来解决;

此时可以换一个思路来想这个问题,无论是用悲观锁,还是乐观锁来处理,最终的结果都是防止库存被扣多了;如果使用悲观锁,如Java锁,或者分布锁,使用锁会降低系统性能和提高代码实现的复杂度,因此没有必要使用悲观锁的方式;

总结

要清楚什么场景下使用乐观锁和悲观锁,一但使用不好,就会增加系统的复杂度; 一般在更新时可以同时做校验的,就可以使用乐观锁; 如果是需要先校验再插入一条新的数据,这时就得考虑悲观锁了;

经验分享 程序员 微信小程序 职场和发展