首页/数据/数据库速查/SCRIPT LOAD / EVALSHA 与脚本缓存管理
Redis 运维与场景124 条

SCRIPT LOAD / EVALSHA 与脚本缓存管理

脚本先 SCRIPT LOAD 拿 sha1,后续用 EVALSHA 只发 40 字节哈希调用,省带宽;缓存不持久化,重启/FLUSH 后调用报 NOSCRIPT,客户端要自动回落 EVAL 并重新加载。

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

脚本缓存管理是什么

Redis 执行 Lua 脚本时会先把脚本内容缓存起来,并返回其 SHA1 摘要:此后客户端只需发送 40 字节的摘要调用(EVALSHA),不必每次带上完整脚本体,省带宽也省解析时间。要记住两点:脚本缓存是内存里的、重启或 SCRIPT FLUSH 后清空;缓存中没有摘要时调用会返回 NOSCRIPT 错误,客户端必须能自动回退到 EVAL 并重新加载。

基本信息

适用版本Redis 2.6+ 支持 Lua 脚本,7.0 起默认使用 Lua 5.1(保留 EVAL 语义)
加载SCRIPT LOAD <script> 返回 SHA1 摘要
调用EVALSHA <sha1> <numkeys> <key...> <arg...>
存在性检查SCRIPT EXISTS <sha1>...
清理SCRIPT FLUSH [ASYNC|SYNC],SCRIPT KILL 终止超时脚本
错误码NOSCRIPT 表示缓存中没有该脚本

典型使用场景

  • 高频原子操作:把查改写成脚本减少往返,配合 EVALSHA 节省带宽
  • 限流与计数:用脚本保证检查与自增的原子性
  • 客户端脚本管理:首次 EVAL,后续 EVALSHA,遇 NOSCRIPT 自动重载
  • 版本变更验证:用 SHA1 确认部署的脚本版本正确
  • 故障排查:实例重启后相关功能全部报错,通常就是脚本缓存丢失

加载与调用

加载与调用
SCRIPT LOAD "return redis.call('GET', KEYS[1])"
# 返回 SHA1 摘要,例如:"4e6d8fc8f0f3f0d4..."

# 用摘要调用:只发 40 字节,不用带脚本体
EVALSHA 4e6d8fc8f0f3f0d4... 1 mykey

SCRIPT EXISTS 4e6d8fc8f0f3f0d4...   # 1 表示在缓存中,0 表示不在
SCRIPT FLUSH ASYNC                  # 清空脚本缓存(异步执行避免阻塞)
SCRIPT KILL                         # 终止执行超时的脚本(仅限未写入的脚本)

命令与风险

命令作用注意
SCRIPT LOAD加载脚本并返回 SHA1同一脚本内容摘要固定,可预加载
EVALSHA按摘要执行缓存缺失时报 NOSCRIPT
SCRIPT EXISTS检查摘要是否存在批量检查时返回 0/1 数组
SCRIPT FLUSH清空所有脚本缓存异步方式可减少阻塞
SCRIPT KILL终止正在执行的脚本脚本已执行写命令时无法终止
主流客户端(redis-py、Jedis、Lettuce 等)都内置脚本管理:首次用 EVAL 执行并把脚本与摘要对应缓存到本地,之后用 EVALSHA,遇到 NOSCRIPT 自动回退重试;自研客户端需要自己实现这套逻辑。
脚本里执行过写命令后又超时,SCRIPT KILL 无法终止它:实例会一直阻塞到脚本自然结束或触发 busy-reply 阈值,因此脚本必须短小、避免遍历大 key,并在客户端设置合理的超时与降级策略。

易错点

  • 生产只用 EVALSHA 不处理 NOSCRIPT:实例重启后相关功能全部报错
  • 脚本执行了写命令后超时:SCRIPT KILL 杀不掉,实例阻塞到脚本结束
  • 脚本里遍历大集合或对大 key 做全量读取:单次执行时间不可控,直接阻塞其他请求
  • 脚本中使用的 key 未通过 KEYS 传入(写死键名):集群模式下哈希槽计算错误,报 CROSSSLOT
  • 脚本更新后仍用旧摘要缓存:部署的脚本与实际执行的版本不一致,排查代价高

数据来源与参考

  • Redis 官方文档: SCRIPT LOAD
  • Redis 官方文档: EVALSHA

相关工具

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