必须用lua脚本实现库存扣减,因为get+decr非原子操作会导致超卖;单用decr不校验库存会变为负数;只有lua能原子化执行“判断库存>0并扣减”,eval脚本返回1成功、0不足、-1异常。

Redis秒杀库存扣减必须用 Lua 脚本,否则必然超卖——因为 GET + DECR 两步操作在高并发下无法保证原子性。
为什么单靠 DECR 不行
很多人以为用 DECR stock_key 就能扣减,但没校验库存是否充足。如果库存只剩 1,两个请求同时 DECR,会变成 -1,超卖了。
更糟的是,先 GET 再 DECR 的“读-改-写”流程,在并发下中间可能被其他请求插入,根本不可信。
必须把“判断库存 > 0”和“扣减”塞进同一个原子执行单元——只有 Lua 脚本能办到。
EVAL 执行库存校验+扣减的最小可行脚本
以下脚本返回值约定:1 表示扣减成功,0 表示库存不足,-1 表示其他异常(如 key 不存在):
if redis.call('EXISTS', KEYS[1]) == 0 then
return -1
end
local stock = tonumber(redis.call('GET', KEYS[1]))
if stock
<p>调用方式(以 <a style="color:#f60; text-decoration:underline;" title="redis" href="https://m.php.cn/zt/15737.html" target="_blank">redis</a>-cli 为例):</p><div class="aritcle_card flexRow artxards">
<div class="artcardd flexRow">
<a class="aritcle_card_img" rel="nofollow" href="/xiazai/skill3464" title="Redis Skill - 高性能缓存管理"><img
src="https://img.php.cn/upload/skill/000/000/081/178960683454849.jpg" alt="Redis Skill - 高性能缓存管理" onerror="this.onerror='';this.src='/static/lhimages/moren/morentu.png'" ></a>
<div class="aritcle_card_info flexColumn">
<a rel="nofollow" href="/xiazai/skill3464" title="Redis Skill - 高性能缓存管理" class="overflowclass">Redis Skill - 高性能缓存管理</a>
<p class="overflowclass">Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。</p>
</div>
<a rel="nofollow" href="/xiazai/skill3464" title="Redis Skill - 高性能缓存管理" class="aritcle_card_btn flexRow flexcenter"><b></b><span>下载</span>
</a>
</div>
</div>
<p><code>redis-cli EVAL "$(cat decr_stock.lua)" 1 stock:goods:1001</code></p>
<p>关键点:</p>
-
KEYS[1]是库存 key,必须传入,不能硬编码——否则无法复用 - 用
tonumber()转换,避免字符串比较出错 - 不用
INCRBY -1,直接DECR更语义清晰 - 不依赖客户端时间或本地变量,所有逻辑在 Redis 端完成
实际部署时容易漏掉的三个坑
脚本本身没问题,但线上出问题往往卡在周边:
- 没给库存 key 设置初始值:
SET stock:goods:1001 100必须提前执行,否则GET返回 nil,tonumber(nil)是 0,导致误判为“有库存” - 没配过期时间:
EXPIRE stock:goods:1001 86400必须跟上,否则秒杀结束库存还挂着,影响后续活动 - 脚本被反复
EVAL加载:高频调用下建议先SCRIPT LOAD缓存 SHA1,再用EVALSHA执行,减少网络和解析开销
要不要加分布式锁?
不需要。Lua 脚本在 Redis 单线程中串行执行,天然互斥。加锁反而引入额外延迟和失败分支,还可能因锁过期导致重复扣减。
唯一要注意的是:确保所有扣减都走同一份脚本、同一个 key 结构。如果有人绕过脚本直连 Redis 改库存,那整个机制就失效了——所以服务端入口必须收敛,禁止裸调 DECR。
真正难的不是写脚本,是让所有人只走这一条路,且 key 命名、初始化、过期策略全部对齐。










