1.场景
假设现在库存只有一个商品了,多线程下如何保证最后库存是0而不是负数
2. 方法
- MySQL中的排他锁
update goods set num = num - 1 WHERE id = 1001 and num > 0
排他锁又称为写锁,简称X锁,顾名思义,排他锁就是不能与其他所并存,如一个事务获取了一个数据行的排他锁,其他事务就不能再获取该行的其他锁,包括共享锁和排他锁,但是获取排他锁的事务是可以对数据就行读取和修改。
就是类似于我在执行update操作的时候,这一行是一个事务(默认加了排他锁)。这一行不能被任何其他线程修改和读写
- 采用了版本号的方式
select version from goods WHERE id= 1001;
update goods set num = num - 1, version = version + 1 WHERE id= 1001 AND version = @version(上面查到的version);
- 这种方式采用了版本号的方式,其实也就是CAS的原理。
- 假设此时version = 100, num = 1;100个线程进入到了这里,同时他们select出来版本号都是version = 100。 然后直接update的时候,只有其中一个先update了,同时更新了版本号。那么其他99个在更新的时候,会发觉version并不等于上次select的version,就说明version被其他线程修改过了。那么我就放弃这次update
- 利用redis
利用redis的单线程预减库存。比如商品有100件。那么我在redis存储一个k,v。例如 <gs1001, 100>
每一个用户线程进来,key值就减1,等减到0的时候,全部拒绝剩下的请求。
那么也就是只有100个线程会进入到后续操作。所以一定不会出现超卖的现象
3.总结
- 第二种方式而且还应该在执行该sql语句前增加一个num数目是否大于0的业务逻辑判断,而且该方式还是会加排它锁的。
- 实际上,第一种方法和第二种两种方式解决超卖的方式也有细微的一点区别
- 考虑两个线程,当库存数量为2时。
- 如果是第一种方式,那么两个线程都能成功执行。
- 如果为第二种方式,如果在第一个线程提交事务之前,第二个线程也执行了相同的sql拿到了version值(也就是线程1和线程2拿到了相同的version值),那么这两个线程之间将只有一个线程能够让库存数目减一成功执行。最终库存数目不为0,而为1