script kill仅能安全终止严格只读脚本,即全程仅调用get、exists等纯查询命令,未执行任何写操作(含touch、persist、sort store等隐式写),且未调用子脚本或连错集群节点。

SCRIPT KILL 能安全终止只读死循环脚本,但前提是它确实没执行过任何写命令 —— 这个“只读”必须是严格的,连 redis.call("EXISTS", key) 都算安全,而 redis.call("INCR", key) 一调就失效。
怎么确认脚本真的“只读”且可被 KILL
Redis 不会自动判断你的意图,它只看是否调用了写操作。所谓“只读”,是指整个脚本中所有 redis.call() 都落在以下范围内:
-
GET、EXISTS、TYPE、STRLEN、HEXISTS、SCARD、ZCARD、LLEN等纯查询命令 - 完全没出现
SET、INCR、DEL、HSET、SADD、ZADD、LPUSH等任意修改数据的命令 - 也没调用
redis.pcall()包裹的写命令(一样算)
一旦有任一写操作成功返回,脚本状态立刻变为 UNKILLABLE,后续 SCRIPT KILL 必然报错:(error) UNKILLABLE Sorry the script already executed write commands。
SCRIPT KILL 执行失败的常见原因
即使你自认为写了只读脚本,SCRIPT KILL 仍可能失败,原因很具体:
- 脚本里用了
redis.call("EVAL", ...)或redis.call("EVALSHA", ...)—— 子脚本的写行为也会让父脚本变不可杀 - 你在集群模式下连错了节点:
SCRIPT KILL只作用于当前连接的节点,而脚本实际运行在负责该 key slot 的 master 上 - 脚本已超时并被 Redis 标记为 “killed”,但仍在 CPU 上跑(比如纯
while true do end),此时再发SCRIPT KILL会返回ERR No scripts in execution right now—— 这是假象,不是真结束了 - 客户端未启用
lua-time-limit或设得过大(如默认 5000),导致 Redis 没触发超时检查点,SCRIPT KILL失去介入时机
实操:终止只读死循环的完整流程
别跳步,每一步都影响成败:
- 先确认目标节点:用
CLUSTER KEYSLOT key或redis-cli -c+KEYS推断 slot,然后直连对应 master 节点(不要用-c模式发SCRIPT KILL) - 检查配置:运行
CONFIG GET lua-time-limit,确保值 ≤ 2000;若非如此,先CONFIG SET lua-time-limit 2000再试 - 执行
SCRIPT KILL;若返回OK,说明成功;若返回UNKILLABLE或ERR No scripts...,立即停手 —— 它已经不可控了 - 验证效果:新开一个客户端执行
CLIENT LIST,找cmd=eval且idle值不再增长的连接;或观察INFO commandstats中cmdstat_eval的usec_per_call是否回落
为什么“只读”脚本也容易踩坑
最容易被忽略的是:Redis 对“写命令”的定义比你想象中宽。以下看似无害的操作,都会让脚本变 UNKILLABLE:
-
redis.call("TOUCH", key)—— 修改访问时间,属于写 -
redis.call("PERSIST", key)—— 移除过期时间,属于写 -
redis.call("SORT", key, "STORE", tmp_key)——STORE参数明确触发写入 - 哪怕只是
redis.call("SET", key, "dummy", "NX", "EX", 1),只要NX成功,就已写入
真正安全的只读脚本,应该像手术刀一样精确:只查、不碰、不改、不存。一旦逻辑稍复杂,就别赌“它只是读”,老老实实用 EVALSHA + 客户端超时兜底 —— 因为 SCRIPT KILL 的窗口期极短,且容错率为零。











