并发下单场景下如何校验库存数量避免超卖问题
库存超卖问题落地方案
优先推荐:乐观锁方案(性能最优,无锁冲突等待)
直接将库存校验和扣减合并为单条原子UPDATE语句,依赖数据库自身的原子操作特性避免并发冲突,无需额外加锁:
- 核心扣减SQL:
UPDATE items SET quantity = quantity - #{buyCount} WHERE id = #{itemId} AND quantity >= #{buyCount}; - 执行后判断该SQL的影响行数:
- 影响行数为1:库存扣减成功,继续执行订单插入操作即可
- 影响行数为0:库存不足,直接返回下单失败
- 配套要求:将「库存扣减SQL执行+订单表插入」两个操作包裹在同一个事务中,事务隔离级别使用数据库默认的
READ COMMITTED即可,无需调整更高隔离级别。 - 优化点:如果业务存在短时同商品大量下单的场景,可加最多3次的冲突重试机制,提升用户下单成功率。
高冲突场景可选:悲观锁方案(自动排队执行)
如果同商品并发扣减冲突概率极高,乐观锁重试成功率低,可使用行级悲观锁,让同商品的并发下单请求自动排队:
- 步骤1:开启事务,执行加锁的库存查询SQL:
SELECT quantity FROM items WHERE id = #{itemId} FOR UPDATE;- 注意:必须确保
id字段是索引,否则行锁会升级为表锁,导致全库下单性能雪崩
- 注意:必须确保
- 步骤2:业务层校验库存是否≥下单数量,不足则直接回滚事务返回失败
- 步骤3:校验通过后执行库存扣减UPDATE、订单插入操作,最后提交事务
- 特性:同商品的多个并发请求执行到
FOR UPDATE语句时,会自动等待前一个持有锁的事务结束后再执行,天然实现后提交的订单等待前一个订单处理完成的需求。
常见疑问解答
- 不需要锁全表:锁全表会导致所有商品的下单请求串行执行,性能极差,仅用行级锁或者乐观锁即可解决问题
- 事务是必须的:无论用哪种方案,都需要将库存扣减和订单插入放在同一事务中,避免出现扣了库存没生成订单、或者生成订单没扣库存的数据不一致问题
- 无需额外调整隔离级别:默认的
READ COMMITTED隔离级别已经能满足需求,更高的隔离级别会带来不必要的性能损耗
内容的提问来源于stack exchange,提问作者nikkorfed
相关产品推荐
相关产品推荐

