SCRIPT KILL 只能终止未执行写命令的脚本;一旦调用 redis.call('set') 等写操作,脚本变为 UNKILLABLE,因 Redis 为保障原子性禁止中途终止,否则可能导致数据不一致。

SCRIPT KILL 为什么有时不生效?
直接结论:SCRIPT KILL 只能终止「尚未执行任何写命令」的脚本;一旦脚本调用了 redis.call('set', ...)、redis.call('del', ...) 等修改数据的命令,它就变成 UNKILLABLE —— 这不是 bug,是 Redis 为保障原子性做的硬性限制。
- 现象:执行
SCRIPT KILL后返回(error) UNKILLABLE Sorry the script already executed write commands... - 原因:Redis 要求 Lua 脚本“全有或全无”,中途杀掉可能留下半写状态(比如库存扣了一半、订单建了没发券),这比挂起更危险
- 验证方法:用
redis-cli --stat或监控instantaneous_ops_per_sec,若持续为 0 且客户端卡住,大概率已进入不可杀状态
死循环脚本卡住后,还能做什么?
当 SCRIPT KILL 失效,你只剩两个真实可行的选项,没有中间路线:
-
SHUTDOWN NOSAVE:最常用也最激进。它会立即终止 Redis 进程,且不触发 RDB/AOF 持久化,意味着上一次快照之后的所有写入全部丢失 - 重启进程(非优雅):比如
kill -9 <pid></pid>,效果等同于SHUTDOWN NOSAVE,但绕过了 Redis 自身的清理逻辑,风险略高 - 等待脚本自然结束?别信——只要循环里没
break或超时检查,它就不会停;而 Redis 不会主动中断已写入的脚本
怎么避免下次再被死循环拖垮?
预防远比抢救重要。关键不是“怎么杀”,而是“不让它活过 5 秒”:
- 务必检查
lua-time-limit配置(单位毫秒),默认5000,可在redis.conf中调低(如2000),改完需CONFIG REWRITE或重启 - 所有循环必须带退出兜底:用计数器 +
if i > MAX_LOOP then break end,别依赖外部条件(比如while redis.call('get', 'flag') == '1'可能永远等不到) - 本地调试时加
redis.log(redis.LOG_WARNING, 'loop step '..i),上线前删掉——日志本身不耗时,但能快速定位卡点 - 禁止在脚本中使用
while true do或无界for,这是生产环境红线
EVAL 时传参不当也会伪装成死循环
看起来像卡死,其实只是参数类型错导致逻辑失效,比如:
-
ARGV[1]是字符串"10",但脚本里写了if ARGV[1] > 5 then ...—— Lua 会做字符串比较,"10" > "5"为false,可能意外跳过 break 条件 - 正确写法是显式转数字:
local limit = tonumber(ARGV[1]) or 0 - KEYS 和 ARGV 下标从 1 开始,但新手常写成
KEYS[0]导致nil,后续操作失败却无报错,循环空转

















