Redis 执行 Lua 脚本过程中报错时,核心规则是 "原子性回滚" ------脚本内所有写操作(SET/DEL/HSET 等)要么全部生效,要么全部不生效,不会出现"部分执行"的情况。具体行为需区分 语法错误 和 运行时错误,同时涉及脚本执行的阻塞特性、错误反馈等细节,以下是详细说明:
一、先明确核心原则:Lua 脚本的"原子性执行"
Redis 执行 Lua 脚本时,会将整个脚本作为 单个原子操作 处理:
- 脚本执行期间,Redis 会阻塞其他所有客户端的命令(因为 Redis 是单线程模型);
- 脚本内的所有指令,要么全部执行成功并提交,要么中途报错后 完全回滚(所有已执行的写操作失效);
- 读操作(
GET/HGET等)不会修改数据,报错后无回滚必要,但仍遵循"原子性"(脚本终止,后续指令不执行)。
二、两种错误类型:报错时机与结果不同
1. 语法错误(脚本加载阶段)
若 Lua 脚本存在语法错误(如关键字拼写错误、括号不匹配等),Redis 在 加载脚本时就会直接拒绝执行,脚本不会进入实际运行阶段。
-
具体表现:
- 客户端立即收到语法错误响应(包含错误类型、行号);
- 脚本内无任何指令执行,数据无任何变化;
- 不会阻塞其他命令(因为脚本未开始执行)。
-
示例(语法错误:缺少闭合括号):
luaredis.call("SET", "key1", "value1") -- 缺少右括号,语法错误执行后 Redis 返回错误:
(error) ERR Error compiling script (new function): user_script:1: syntax error near 'SET'
2. 运行时错误(脚本执行阶段)
脚本语法正确但执行中触发错误(如访问不存在的键、类型不匹配、数值越界等),Redis 会 立即终止脚本执行,并回滚所有已执行的写操作。
-
核心行为:
- 脚本执行到报错行时停止,后续所有指令不再执行;
- 脚本内已执行的 所有写操作(SET/DEL 等)全部失效(数据恢复到脚本执行前的状态);
- 读操作(GET 等)的结果不会影响数据,但仅执行到报错前的读指令;
- 客户端收到运行时错误响应(包含错误原因、行号);
- 脚本执行期间仍会阻塞其他客户端命令,直到报错终止。
-
示例(运行时错误:类型不匹配):
脚本逻辑:先设置
key1为字符串,再尝试对其做自增(INCR仅支持数字类型):luaredis.call("SET", "key1", "hello") -- 写操作1:设置字符串 redis.call("INCR", "key1") -- 运行时错误:字符串无法自增 redis.call("SET", "key2", "world") -- 后续指令:不会执行执行后结果:
- 报错信息:
(error) ERR Error running script (call to f_xxxx): @user_script:2: ERR value is not an integer or out of range; - 数据状态:
key1未被设置(写操作1回滚),key2不存在; - 其他客户端在脚本执行期间的命令被阻塞,报错后恢复正常。
- 报错信息:
三、关键补充:特殊场景与细节
1. redis.call() 与 redis.pcall() 的差异(影响报错行为)
Lua 脚本中调用 Redis 命令有两种方式,报错处理不同:
redis.call():触发错误时 直接抛出异常,导致脚本终止(即上述"运行时错误"的默认行为);redis.pcall():触发错误时 捕获异常并返回错误信息,脚本可继续执行后续指令(但写操作仍遵循原子性,若后续无报错,所有写操作生效;若后续仍报错,全回滚)。
示例(用 pcall 捕获错误):
lua
-- 用 pcall 调用 INCR,捕获错误不终止脚本
local res1 = redis.pcall("INCR", "key1") -- key1 是字符串,返回错误信息
redis.call("SET", "key2", "value2") -- 会正常执行
执行结果:
- 脚本正常结束,
key2被成功设置; res1存储错误信息(可通过 Lua 逻辑处理);- 若后续
SET key2报错,key2会回滚,key1无变化。
2. 事务相关命令(MULTI/EXEC)在脚本中的特殊处理
若 Lua 脚本内使用 MULTI/EXEC 包裹命令,报错规则不变:
- 若
EXEC前的命令报错(如redis.call("SET", "key", 123)正常,后续redis.call("INCR", "key")正常),则事务提交; - 若
EXEC前或EXEC中任一命令报错,整个脚本的所有写操作(包括事务内的)全部回滚。
注意:Redis 不推荐在 Lua 脚本内嵌套事务(脚本本身已保证原子性,嵌套事务无意义)。
3. 脚本执行超时与报错的关联
Redis 有 Lua 脚本超时限制(默认 5 秒,通过 lua-time-limit 配置):
- 若脚本执行时间超过阈值,Redis 会发送"停止信号"给 Lua 解释器,但 Lua 脚本本身不会立即终止(需手动处理信号);
- 若脚本未响应停止信号,Redis 会在下次重启时拒绝加载该脚本(避免死循环);
- 超时本身不会直接导致"部分执行"------若脚本最终被终止,仍遵循原子性回滚。
实际开发建议:
- 脚本执行前先通过
redis-cli --eval script.lua本地测试,避免语法错误; - 关键写操作前做参数校验(如用 Lua 逻辑判断键类型、数值范围),减少运行时错误;
- 避免脚本过长或包含耗时操作(如循环遍历大量键),防止阻塞 Redis。