秒杀系统中如何处理超卖问题
超卖是秒杀系统中最核心、最棘手的问题。其本质是多个并发请求同时查询库存并执行扣减操作,由于操作缺乏原子性或隔离性,导致实际扣减数量超过真实库存。例如库存10件,最终却生成15笔订单。下面从多个维度系统性地梳理处理超卖的主流方案,这些方案在实践中往往组合使用。
一、数据库层方案:悲观锁与乐观锁
1. 悲观锁(SELECT FOR UPDATE)
在查询库存时直接对数据库记录加锁,其他事务必须等待锁释放才能操作。这种方式强一致性,但缺点是锁冲突严重时性能极差,且容易引发死锁,仅适用于低并发场景。
2. 乐观锁(CAS + 版本号 / 条件更新)
在更新库存时,通过WHERE stock > 0 AND version = ?等条件判断库存是否被改变。如果更新影响行数为0,表示库存已被其他请求修改,本次操作失败并重试。优点是无锁化、性能较好;缺点是高并发下重试率飙升,用户体验下降,适合中等并发场景。
UPDATE products SET stock = stock - 1, version = version + 1 WHERE id = ? AND stock > 0 AND version = ?;二、缓存层方案:Redis原子操作与Lua脚本
这是当前秒杀系统最主流的防超卖手段,因为Redis基于内存、单线程模型天然支持原子操作,能承载每秒数万到数十万QPS。
1. Redis原子指令(DECR/INCRBY)
直接使用DECR扣减库存,并在扣减后判断返回值是否小于0。简单快速,但无法实现“先判断后扣减”的复合逻辑,存在一定超卖风险(如多个请求同时通过 if 检查)。
2. 基于Lua脚本的原子扣减(推荐)
Lua脚本在Redis服务端执行,整个脚本作为一个原子
