一提到 Redis 原子性,很多人会直接想到 Lua 或 MULTI/EXEC。它们确实能让一段逻辑在 Redis 主线程上连续执行,但这不等于关系数据库里的事务。

Redis 事务没有自动回滚;Lua 原子执行,但执行期间会阻塞其他命令;WATCH 是乐观锁,冲突后需要客户端重试。

先把机制边界说清楚

这一篇讨论 Redis 内部的原子执行边界,不讨论分布式事务。Redis 可以保证单实例命令序列的执行语义,不能替代跨系统一致性协议。

整体路径

Pipeline、事务、Lua 的边界

上面这张图先把主线铺开:减 RTT、队列顺序和脚本原子性是三件事。判断该用哪种,看你需要的是网络优化(Pipeline)、命令不被穿插(MULTI)还是整体原子(Lua/Functions),三者代价完全不同。

底层机制

  • MULTI 后命令先入队,EXEC 时按顺序执行,中间不会穿插其他客户端命令。
  • Redis 事务的出错语义要分两段记,这是高频翻车点:① 入队期错误(命令名拼错、参数不对等语法/参数错误)——命令无法 QUEUED,EXEC 会直接拒绝执行,整批一条都不跑;② 执行期错误(如对字符串类型的 key 执行 INCR)——出错那条报错,后面命令继续执行,前面已执行的也不回滚。所以「MULTI 里某条命令出错后面还执行吗」的正确答案是「看错在哪:入队错就全不执行,执行错就后续继续」。
  • Redis 事务不提供传统回滚,这是设计选择(Redis 作者认为语法错误应该由客户端保证,运行期错误应保持简单不引入回滚复杂度)。
  • WATCH 通过监视 key 的修改实现乐观并发控制,冲突后 EXEC 返回失败(nil),客户端需重试。
  • Lua 和 Functions 在服务器端执行,可以减少网络往返,但脚本必须短小可控。Lua 执行期间会独占主线程,阻塞所有其他命令——这正是它「原子」的代价。lua-time-limit 默认 5 秒,超过后 Redis 只打日志记录、不强行中断(因为中断可能破坏数据一致性);要终止需用 SCRIPT KILL,但它只能 kill 还没执行写操作的脚本,一旦脚本已经执行了写命令,只能 SHUTDOWN NOSAVE
  • Functions(Redis 7.0 引入)是 Lua eval 的升级:eval 是「临时脚本」,靠 SHA 缓存,随连接或重启可能丢失;Functions 是「持久化注册」,按名调用、重启不丢、可热更新。新项目推荐用 Functions 替代散落的 eval。

把复杂业务逻辑塞进 Lua,会把 Redis 主线程变成业务执行器。原子性越强,阻塞风险越集中。

取舍与边界

把复杂业务逻辑塞进 Lua,会把 Redis 主线程变成业务执行器。原子性越强,阻塞风险越集中。

典型问题:用机制化例子排查

  • 库存扣减、限流计数、条件删除适合短 Lua。
  • 复杂订单流程、外部 RPC、循环扫描不应该写进 Lua。
  • 脚本要有 key 参数约束,Cluster 下避免跨 slot。
  • 客户端要处理 WATCH 冲突和脚本超时,不要假设一次必成功。

收束:一句判断

Redis 的原子性适合小而确定的状态变更,不适合承载整段业务事务。


关于十三Tech

我是十三,All in AI Agent 方向的架构师,专注 AI 工程实践。

我相信 AI 是程序员的最佳搭档,也希望帮助每一位开发者更好地驾驭 AI。

如果你想继续跟完这套「图解 Redis」,欢迎关注公众号 「十三Tech」。后续会继续按数据结构、底层机制、持久化、高可用和实战排查这条线更新。

十三Tech公众号二维码