Redis不支持分布式事务,MULTI/EXEC仅限单实例原子执行且无回滚;Lua脚本可原子执行但受限于超时、阻塞命令和集群slot约束;强一致场景应改用消息队列、TCC或Seata等方案。

Redis 本身不支持传统意义上的分布式事务
Redis 没有像 MySQL 那样的 START TRANSACTION / COMMIT / ROLLBACK 机制,也没有跨 key、跨节点的原子性事务保证。它的 MULTI / EXEC 只是把多个命令打包成一个原子执行序列,但仅限于单个 Redis 实例(或单个分片),且不提供回滚能力——一旦某个命令在 EXEC 中报错,其余命令照常执行,错误不会中断流程,也不会自动撤销已执行的操作。
常见错误现象:EXEC 返回结果里某一项是 nil 或 error,但前面的 SET、INCR 已生效,业务以为“整个操作失败”而没做补偿,导致状态不一致。
- 使用场景:适合对一致性要求不极端的场景,比如计数器批量更新、缓存字段协同写入
- 参数差异:
WATCH可实现乐观锁式条件执行,但只监控 key 是否被其他客户端修改,不能监控值内容变化 - 性能影响:
MULTI块内命令仍串行执行,无并发提升;WATCH失败会触发重试逻辑,可能放大延迟
用 Lua 脚本替代事务能解决什么问题
Lua 脚本在 Redis 中是原子执行的,整个脚本运行期间不会被其他命令打断,这比 MULTI/EXEC 更可靠,尤其适合需要判断+写入组合逻辑的场景(比如“如果库存 > 0 则扣减并记录订单号”)。
但要注意:Lua 脚本不能调用阻塞命令(如 BLPOP),也不能访问外部系统;超时默认 5 秒(lua-time-limit 配置),超时会导致脚本被 kill 并可能留下中间状态。
- 示例中必须用
redis.call()而非redis.pcall()来确保错误中断(后者会捕获异常并返回 table,脚本继续往下走) - 所有 key 必须显式传入
KEYS数组,集群模式下这些 key 必须落在同一个 slot,否则报CROSSSLOT错误 - 脚本不宜过长,否则影响 Redis 主线程响应,也增加 AOF 重放负担
真正需要分布式事务时,该绕开 Redis 还是补一层
如果你的业务要求“缓存更新 + DB 写入”强一致(比如支付成功后必须同时落库和刷新用户余额缓存),Redis 不是合适的事务载体。它无法协调外部数据库,也无法参与两阶段提交(2PC)。
这时候常见做法不是硬套 Redis 事务,而是换思路:
- 用消息队列解耦:DB 提交成功后发消息,由消费者异步刷新缓存,配合幂等和重试应对失败
- 用 TCC 模式:把缓存操作拆成
Try(预占)、Confirm(提交)、Cancel(释放),但需业务自己维护中间状态 - 借助 Seata 等框架:它们把 Redis 当作一个“资源管理器”接入,本质是靠本地事务表 + 补偿日志模拟,不是 Redis 自身能力
WATCH + MULTI 的实际使用边界在哪
WATCH 适合 key 竞争不激烈、冲突概率低的场景,比如用户资料编辑(一个人改自己资料,极少并发);但它不适合高竞争写入(如秒杀库存扣减),因为冲突重试成本高,还可能引发“写放大”——每次失败都要重新读取、计算、再 WATCH、再 EXEC。
容易踩的坑:
-
WATCH后执行了任何写命令(包括GET以外的读,比如EXISTS),都会让 watch 失效 -
WATCH的 key 如果被DEL或过期删除,也算“被修改”,导致EXEC失败 - 集群环境下,
WATCH只对当前节点有效,跨 slot 的 key 无法统一监控
真正难的从来不是写个 EXEC 或塞段 Lua,而是想清楚:这个操作到底需不需要跨系统强一致?如果需要,Redis 就不该站在事务中心位置。

















