首页/数据/数据库速查/EVAL 服务端脚本(原子复合操作)
Redis 运维与场景124 条

EVAL 服务端脚本(原子复合操作)

EVAL 把 Lua 脚本发送到服务端原子执行:读-判断-写 一步完成,不需要 WATCH 重试;库存扣减、限流、锁释放都靠它。脚本内只用 KEYS/ARGV,别写慢逻辑。

数据库速查数据更新于 2026-10-13

EVAL 是什么

EVAL 把一段 Lua 脚本发送到服务端执行,整个脚本作为一个命令运行,中间的读、判断、写不会被其他命令插入,因此不需要 WATCH 重试。库存扣减、限流计数、锁释放这些「读完再判断再写」的逻辑用它一次完成最稳;代价是脚本运行期间会占住单线程实例,脚本必须短小。

基本信息

适用版本EVAL 需 Redis 2.6+;EVAL_RO 需 7.0+
参数传递键放 KEYS、值放 ARGV,不要拼进脚本文本
执行方式EVAL 直接传脚本;EVALSHA 配合 SCRIPT LOAD 省带宽
耗时上限lua-time-limit 默认 5000 毫秒,超时只记录不快停
集群限制脚本涉及的所有 key 必须落在同一槽,可用 hash tag 保证

典型使用场景

  • 秒杀扣库存:判断与 DECR 在一段脚本里完成,杜绝超卖
  • 接口限流:INCR 与首次设过期原子完成,避免计数无过期时间
  • 分布式锁释放:GET 比对 token 与 DEL 合成一步
  • 批量条件写入:按当前状态决定是否更新,避免多轮往返
  • 多键一致性读:一次脚本内读取多个键,避免读到中间状态
  • 降低网络往返:高频小操作合并成一次调用,减少 RTT

示例

示例
-- KEYS[1]=库存 key;返回剩余库存,-1 表示无货
local stock = tonumber(redis.call('GET', KEYS[1]) or '-1')
if stock <= 0 then return -1 end
redis.call('DECR', KEYS[1])
return stock - 1

EVAL "local s = tonumber(redis.call('GET', KEYS[1]) or '-1'); if s <= 0 then return -1 end; redis.call('DECR', KEYS[1]); return s - 1" 1 stock:1001
SCRIPT LOAD "return redis.call('INCR', KEYS[1])"   # 返回 sha1,之后用 EVALSHA 复用
EVALSHA 3c9c1b6e8f0a1d2c3b4a5e6f7a8b9c0d1e2f3a4b 1 counter:api

返回值映射

Lua 返回客户端收到用途
number整数库存余量、计数结果
string批量字符串状态描述或拼接结果
table数组一批键值结果
true / 1整数 1操作成功标记
false / nilnil未命中等空语义
脚本运行期间阻塞整个实例:只写 O(1) 或 O(logN) 级逻辑;大集合遍历、全量 SCAN 千万别放进来。
脚本长时间运行会导致集群内所有请求排队:发现脚本卡住时无法直接 KILL,只能等服务端触发超时行为。上线前用 SCRIPT LOAD 加真实数据量做验证。
Redis 7.0 起提供 EVAL_RO 与 EVALSHA_RO,脚本内只允许只读命令,统计类场景用它可以避免误写;只读脚本也能在只读副本上执行,写入脚本则不行。

易错点

  • 在脚本里做全量遍历或大计算,主线程被占住,整个实例请求堆积
  • 把键名拼进脚本文本而不是走 KEYS:Cluster 下路由失败,且脚本缓存无法复用
  • 集群中操作的多个 key 不在同一槽,直接报 CROSSSLOT 错误
  • 脚本里用了随机或时间相关的写值,导致主从复制与 AOF 回放结果不一致
  • 每次调用都用 EVAL 传完整脚本,脚本较大时网络开销明显,应改用 SCRIPT LOAD 加 EVALSHA

数据来源与参考

  • Redis 官方文档: EVAL
  • Redis 官方文档: Lua scripting

相关工具

​‌‌​​‌​​​‌‌​‌‌‌‌​‌‌​​​‌‌​‌‌‌​​‌‌​​‌​‌‌‌​​‌‌‌​​​‌​‌‌​‌​​‌​‌‌‌​​​‌​‌‌​‌​​‌​‌‌​​​‌‌​‌‌‌​‌​​​‌‌​‌‌‌‌​​‌​‌‌‌​​‌‌​​​‌‌​‌‌​‌‌‌‌​‌‌​‌‌​‌