Skip to content

Redis 支持事务吗?如何实现? ​

  • 标签:后端、Redis

🏛 业务架构师 ​

支持,但要先泼冷水:Redis 事务不是数据库事务。MULTI 开启 → 命令入队 → EXEC 原子执行。两个关键局限:

  • 不保证回滚:入队命令有语法错误会整体不执行;但执行期错误(比如对 String 执行 LPUSH)不回滚已执行的其他命令——与 MySQL 事务的原子性承诺完全不同;
  • 不是隔离的 ACID 事务:没有隔离级别、没有回滚段,它只是"把一批命令打包、免插队地顺序执行"(执行期间不被其他客户端命令打断),WATCH 提供乐观锁式的一致性(EXEC 前被改则失败,靠业务重试)。

业务视角的正确打开方式:

  1. 需要"多个命令原子执行"时,优先 Lua 脚本——脚本整体执行语义清晰、错误处理在脚本内可控(库存扣减、限流判定都是 Lua 而非 MULTI 的原因);MULTI 适合"命令间无依赖、只是不想被插队"的批量写;
  2. 跨资源的一致性别指望 Redis 事务:"Redis 记数 + MySQL 扣减"的原子性靠分布式方案(事务消息/本地消息表/TCC),Redis 事务的作用域只到它自己;
  3. WATCH 的乐观并发适合"读-改-写"冲突少的场景(如账户状态检查后更新),冲突激烈就退化成重试风暴,改用 Lua。

见过把"Redis 事务"当 ACID 用、出执行期错误后数据停在半路的事故——工具语义理解错,方案再精巧也是错的地基。

一句话:Redis 事务 = 批量原子执行器,不是数据库事务;命名带来的误解本身就是这题最大的考点。

🏗 系统架构师 ​

支持,但它是弱事务——保证队列内命令串行执行与隔离,不保证原子回滚,也没有回滚能力:

用法(四条命令):

MULTI      # 开启,后续命令入队
SET k v    # 入队,返回 QUEUED
INCR cnt   # 入队
EXEC       # 一次性顺序执行,返回各命令结果

中间可 DISCARD 放弃。WATCH key 可以做乐观锁:EXEC 前被 WATCH 的 key 变了,事务整体放弃返回 nil(check-and-set 的实现基础)。

三条边界(架构上必须讲清):

  • 不回滚:入队阶段语法检查(编译期错误)整个事务拒绝执行;但运行时错误(比如对 String INCR)只失败那一条,其余照常提交——没有部分回滚;
  • 执行期间不被插队:EXEC 后队列内命令串行执行,保证隔离性;但 MULTI 到 EXEC 之间的命令只是入队,不阻塞其他客户端;
  • 官方态度:因为不需要回滚(错误只该是开发错误),Redis 保持了简单高效;需要复杂原子性用 Lua 脚本(脚本是真正原子的,且能读取中间结果做条件逻辑,事务命令间不能互相读结果)。

我的实践:简单批量用 pipeline(无事务语义,纯省 RTT);条件写(CAS)用 WATCH 或 Lua;真正多对象一致的业务动作,用 Lua 组合或上应用层补偿。

💻 全栈开发专家 ​

支持,但它是"弱事务"——只保证命令排队顺序执行,不保证原子回滚。

redis
MULTI                     # 开启事务,后续命令入队
SET stock:1 100
INCRBY stock:1 -1
EXEC                      # 顺序执行;执行前 WATCH 的 key 变了则放弃(返回 nil)

三个必须说清的特性:

  1. 入队时语法错误(命令不存在),EXEC 时整个事务不执行;
  2. 运行时错误(比如对 String 做 LPUSH),该条失败,其余照常执行,不回滚——这是和关系型事务的本质区别。官方理由:错误只该由开发 bug 造成,回滚支持不值得拖慢所有人。
  3. WATCH 实现乐观锁:EXEC 前被 WATCH 的 key 被别的客户端改了,事务放弃,配合重试可实现 CAS 语义。

我的实践:需要"原子性 + 条件判断"的场景,Lua 脚本比事务更合适——脚本单线程原子执行,中途可以读中间值做判断;MULTI/EXEC 里间做不到(入队阶段拿不到结果)。

bash
EVAL "local s = tonumber(redis.call('GET', KEYS[1]))
      if s and s >= tonumber(ARGV[1]) then
        return redis.call('DECRBY', KEYS[1], ARGV[1])
      end
      return -1" 1 stock:1 1

另外:事务里的命令在 EXEC 前不执行,不能在 MULTI 里读中间结果做分支,这也是我选 Lua 的原因。