减库存
类型
- 下单减库存,即当买家下单后,在商品的总库存中减去买家购买数量。下单减库存是最简单的减库存方式,也是控制最精确的一种,下单时直接通过数据库的事务机制控制商品库存,这样一定不会出现超卖的情况。
- 容易恶意下单
- 付款减库存,即买家下单后,并不立即减库存,而是等到有用户付款后才真正减库存,否则库存一直保留给其他买家。
- 但因为付款时才减库存,如果并发比较高,有可能出现买家下单后付不了款的情况,因为可能商品已经被其他人买走了。
- 而且涉及支付业务,容易造成超卖
- 预扣库存,买家下单后,库存为其保留一定的时间(如 10 分钟),超过这个时间,库存将会自动释放,释放后其他买家就可以继续购买。
- 在买家付款前,系统会校验该订单的库存是否还有保留:如果没有保留,则再次尝试预扣;如果库存不足(也就是预扣失败)则不允许继续付款;如果预扣成功,则完成付款并实际地减去库存。
- 如果被长时间恶意下单,没啥用
缺点和解决方案
- 下单减库存:容易被恶意下单
- 付款减库存:涉及支付等服务,容易造成超卖,用户无法付款,体验差
- 预扣库存:如果被长时间恶意下单,也有一定的风险
场景
- 普通用预扣库存,可以结合安全和反作弊的措施来制止恶意下单的操作
- 给经常下单不付款的买家进行识别打标(可以在被打标的买家下单时不减库存)、给某些类目设置最大购买件数(例如,参加活动的商品一人最多只能买 3 件),以及对重复下单不付款的操作进行次数限制等
- 秒杀场景可以使用下单减库存的操作
- 由于并发量高,用性能高的优先
- 秒杀场合,一般被恶意下单的几率较小
- 保证数据一致性,主要就是保证大并发请求时库存数据不能为负数,也就是要保证数据库中的库存字段值不能为负数
- 是在应用程序中通过事务来判断,即保证减后库存不能为负数,否则就回滚
- 直接设置数据库的字段数据为无符号整数,这样减后库存字段值小于零时会直接执行 SQL 语句来报错
- 使用 CASE WHEN 判断语句,例如这样的 SQL 语句:
UPDATE item SET inventory = CASE WHEN inventory >= xxx THEN inventory-xxx ELSE inventory END
- 操作:先创建订单但是先不生效,然后减库存,如果减库存成功后再生效订单,否则订单不生效
待切分
简单逻辑
判断当前用户是否抢购果当前商品
- 判断商品是否符合要求 代码一
- 扣减库存 代码二
- 判断扣减库存是否成功
- 成功
- 双重检查,可以使用分布式锁进行包裹
- 当前用是否抢购过当前商品 && 库存减一操作
- 双重检查,可以使用分布式锁进行包裹
- 失败
- 成功
代码一
select
A.*,
B.name as itemName,
(
-- 处于秒杀时间内且数量大于1
case when (now() BETWEEN A.start_time AND A.end_time AND A.total > 0)
then 1
else 0
end
)
from product_kill as A left join product B on A.id = B.item_id
where A.is_active
[and A.]
代码二
update product_kill
set total = total - 1
where id = <id>
-- 商品必须大于0,防止超卖
and total > 0;
extra
原因
- 事务并行,get then update
方案
db
- 串行化 no
- 修改记录对记录加锁 no 会死锁
- 乐观锁,使用 version
- get 获得 before_version
- set if current_version = before_version
- get 获得 before_version
redis
- 两个值,
product_count
和kill_success_user_list
MyJedisPool.execute(jedis -> {
jedis.watch("product_count", "kill_success_user_list");
int num = Integer.parseInt(jedis.get("product_count"));
System.out.println(num);
if (num > 0) {
Transaction transaction = jedis.multi();
transaction.decr("product_count");
transaction.rpush("kill_success_user_list", idsList.pop());
transaction.exec();
}
});
方式二
- 我 jio 得用 lua 判断 decrement 后 库存 > 0
- 判断为 true 则 然后发送 mq
- 判断为 true 则 然后发送 mq