必须用 Lua 脚本实现库存扣减,因其在 Redis 单线程中原子执行“读-判-减”,避免高并发超卖;DECRBY 无条件扣减,无法满足“有货才扣”需求。

必须用 Lua 脚本,否则高并发下必然超卖。 因为 Redis 单线程执行 Lua 脚本,整个“读库存→比大小→扣减”过程不可打断;而拆成 GET + DECRBY 两步,中间必然被其他请求插入,导致多个请求同时看到库存=1、都通过判断、都扣减,最终库存变负。
为什么不能直接用 DECRBY?
DECRBY 本身是原子的,但它不带条件判断。它只会无脑减,哪怕库存已经是 -5,照样继续减。秒杀场景要的是“有货才扣”,不是“不管有没有都扣”。所以必须把 GET 和判断逻辑也塞进服务端执行,而 Lua 是唯一能干这事的标准方式。
Lua 脚本里最容易踩的三个坑
常见错误现象:ERR Error running script (call to f_): @user_script:... 多半栽在这几处:
-
KEYS[1]必须显式传入,脚本里不能硬写 key 名(集群模式会报错) -
ARGV[1]是字符串,要用tonumber(ARGV[1])转数字,否则比较或计算会出错 - 不能调用
TIME、KEYS、SCAN等非确定性或集群不支持的命令
一个生产可用的库存扣减 Lua 脚本
这个脚本返回值语义清晰:-1 表示 key 不存在,0 表示库存不足,正数表示扣减后剩余库存:
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
local key = KEYS[1]
local delta = tonumber(ARGV[1])
local current = redis.call("GET", key)
if current == false then
return -1
end
current = tonumber(current)
if current < delta then
return 0
end
return redis.call("DECRBY", key, delta)
调用时注意:KEYS 数组只传 1 个 key(如 "seckill:stock:1001"),ARGV 数组只传 1 个数量字符串(如 "1")。别多传、别少传、别传错类型。
Java 或 PHP 调用后怎么判断结果?
别只看是否抛异常,重点看返回值:
- 返回
-1:库存 key 根本没初始化,该补初始值了(比如用SET seckill:stock:1001 100 EX 86400) - 返回
0:库存不足,前端可直接提示“手慢了” - 返回 ≥0 的整数:扣减成功,该值就是新剩余库存,建议记日志用于对账
真正难的不是写脚本,而是确保每次扣减都走这个脚本——任何绕过它的路径(比如后台运营手动 SET、数据库同步直写 Redis)都会悄悄破坏一致性。防超卖的防线,永远是最薄弱的那个环节决定的。

















