Go中用go-redis执行Lua脚本需用Script.Run()自动EVALSHA以提升性能30%以上,首次须SCRIPT LOAD;动态脚本退化为Eval但需控长防超时;参数传递须严格匹配KEYS/ARGV顺序与类型,调试优先用redis-cli eval。

直接在 Go 中用 go-redis 执行 Lua 脚本,能避免多次网络往返、保证原子性,但不是所有场景都适合——关键看脚本是否读写同一组 key、是否依赖 Redis 内部状态(比如 redis.call("GET", ...)),否则容易踩超时、沙箱限制或结果类型不匹配的坑。
为什么必须用 EVALSHA 而不是反复 EVAL
Redis 会缓存已加载的 Lua 脚本 SHA1 值,EVALSHA 复用缓存后,性能比每次传完整脚本的 EVAL 高 30% 以上。但首次执行必须先 SCRIPT LOAD,否则返回 NOSCRIPT 错误。
- Go 中推荐用
redis.Script.Load().Run()加载一次,再用redis.Script.Run()(内部自动用EVALSHA) - 不要手动拼接
SCRIPT LOAD+EVALSHA,go-redis的Script类型已封装好容错逻辑 - 如果脚本内容动态生成(比如带变量插值),就不能用
Script对象,得退回到Eval,但此时务必控制脚本长度,避免触发lua-time-limit超时
Script.Run() 的参数传递规则很严格
Lua 脚本里通过 KEYS[1]、ARGV[1] 访问参数,而 Script.Run() 的第 3 个参数是 keys []string,第 4 个是 args ...interface{}。顺序错、类型错、空切片处理不当都会导致脚本报错或逻辑异常。
-
keys必须是非空切片,哪怕只用一个 key,也要传[]string{"user:123"},不能传nil或空字符串 -
args中的int、float64会被转成 Lua number,string变成 string,但nil会变成 Luanil,而redis.call()不接受nil作参数 - 如果需要传 JSON 或复杂结构,先在 Go 侧
json.Marshal成字符串,再在 Lua 里用cjson.decode()(需 Redis 启用cjson模块)
本地调试 Lua 脚本比在 Go 里试错快得多
Redis CLI 的 eval 命令支持快速验证逻辑,还能用 redis.log() 输出调试信息(日志出现在 Redis 的 loglevel notice 及以上)。
- CLI 示例:
redis-cli --eval ./incr_with_limit.lua user:123 , 100 10(注意逗号前后空格,左边是 keys,右边是 args) - Go 中捕获错误时,别只打印
err.Error(),要检查err是否为*redis.RedisError,然后用.Error()看原始 Redis 错误,比如(error) ERR Error running script (call to f_...): @user_script:3: user_script:3: attempt to compare number with nil - 脚本中避免用
for i=1, #KEYS do这类循环,如果KEYS为空,#KEYS是 0,循环不会执行——但你可能预期它报错提示缺失 key
最常被忽略的是脚本超时和内存限制:Redis 默认 lua-time-limit 5000(毫秒),且不允许脚本分配超过 10MB 内存。一旦触发,Redis 会强制中断并返回 BUSY 或 OOM,此时 Go 侧收到的是连接错误而非 Lua 异常,需要单独处理重试或降级逻辑。











