redis事务multi/exec不具原子性,仅命令排队执行,错误不中断不回滚;lua脚本通过单线程强保障实现真正原子性,支持条件判断与多步逻辑闭环,watch仅为乐观锁补丁。

Redis事务 MULTI/EXEC 不是真正原子执行
很多人误以为 MULTI 开启的事务像数据库那样“全成功或全失败”,实际它只是把命令排队,EXEC 时挨个执行——中间某条失败(比如 INCR 一个字符串值),后续命令照常运行,不会中断,也不会回滚。
常见错误现象:GET 命令在事务中返回 nil 或旧值,因为事务里不支持“读-判断-写”逻辑;转账场景中,GET a 得到 1000,但 EXEC 前被其他客户端改成了 800,事务仍会用 1000 做计算,导致数据错乱。
-
MULTI后所有命令只是入队,不校验语法、类型或 key 存在性 - 事务内无法用前一条命令结果影响后一条(无变量、无分支)
- 网络交互次数 = 命令数 + 2(
MULTI、EXEC各一次)
Lua脚本的原子性是单线程强保障
EVAL 执行的 Lua 脚本在 Redis 单线程中**完整跑完才释放控制权**,期间不会有其他客户端命令插入。这意味着:条件判断、多次 redis.call()、临时变量赋值、错误中断——全部在一个不可分割的时间片里完成。
使用场景典型如库存扣减:if tonumber(redis.call("GET", KEYS[1])) >= tonumber(ARGV[1]) then ...,先读、再判、再写,三步一气呵成。
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
- 脚本中任意
redis.call()报错(如 key 不存在、类型不匹配),整个脚本立刻终止,后续语句不执行 - 没有自动回滚,但“没执行=没发生”,比事务中部分执行更可控
- 一次网络请求提交整个逻辑,省去多次 round-trip
WATCH 实际上是事务的补丁,不是替代方案
WATCH 看似让事务支持“检查后执行”,但它本质是乐观锁:只在 EXEC 时比对 key 的版本,一旦被改就整个事务返回 nil。这要求客户端自己重试,且重试逻辑必须重新 WATCH + MULTI + EXEC,容易写错或漏处理边界。
而 Lua 脚本天然自带“检查+执行”闭环,不需要外部重试机制。
-
WATCH只能监控 key,不能监控 value 变化或复合条件 - 多个
WATCHkey 时,任一被改,整个事务就废,成功率随并发上升而下降 - 脚本中可用
redis.call("GET", ...)实时读取并参与逻辑,更灵活
性能与可维护性差异直接决定选型
高并发下,Lua 脚本的吞吐优势明显:一个 EVAL 替代 5 条命令的事务,网络开销减少 80% 以上;脚本逻辑集中,调试和灰度发布也比分散的客户端重试逻辑更可靠。
但要注意:Lua 脚本执行时间过长会阻塞 Redis 其他请求。生产环境必须设 lua-time-limit,且脚本里避免循环、sleep、大 key 遍历等危险操作。
- 简单计数器、存在性判断(
SETNX类)用原生命令最轻量 - 涉及读+条件+写的多步逻辑,优先写 Lua 脚本,别硬套
MULTI/EXEC - 脚本上线前务必在测试环境模拟超时场景,验证
SCRIPT KILL是否能及时介入
EVAL 调用,不输出脚本内部变量或行号。上线前必须带完备的 redis.log() 和异常兜底。










