核心一句话:Redis 事务不是传统数据库的事务,不支持回滚;它是一组命令的批量排队、一次性串行执行,保证这一批命令连续执行不被其他客户端打断。
一、事务基础命令
Redis 事务由 4 个核心指令组成:
MULTI:开启事务,标记事务开始。后续输入的命令不会立刻执行,而是放入事务队列。COMMAND:普通命令,入队,返回QUEUED。EXEC:提交事务,一次性、按顺序执行队列里所有命令。DISCARD:放弃事务,清空命令队列,退出事务状态。
额外:WATCH key [key ...]:乐观锁,事务执行前监控 key,如果在 MULTI 之后、EXEC 之前被其他客户端修改,事务直接执行失败。 UNWATCH:取消监控。
二、执行流程
客户端发送
MULTI→ Redis 将该客户端标记为事务状态。客户端依次发送 set/get/hset 等命令:
Redis不执行,仅校验语法,合法则压入事务队列,返回
QUEUED;如果命令语法错误,直接报错,命令不会入队。
客户端发送
EXEC:Redis 拿到当前客户端的事务队列,按顺序串行执行所有命令;
执行期间,不会插入其他客户端的任何命令;
全部执行完成后,一次性把所有结果返回客户端。
执行完毕,客户端退出事务状态。
⚠️ 重点区分两种错误:
入队前语法错误:整个事务
EXEC会拒绝执行,全部不跑;入队成功,但执行时运行时错误(比如对字符串做
LPOP):错误的命令失败,其他命令继续执行,不会回滚! Redis 没有 undo log,不支持回滚。
示例:
MULTI
SET a 1
INCR b ; b不存在,入队成功,QUEUED
EXEC
执行结果:SET a 1 成功,INCR b 报错,a=1 保留,不会回滚。
三、WATCH 乐观锁实现机制
WATCH 是 Redis 事务实现原子性的关键,基于版本校验的乐观锁:
WATCH k1 k2:Redis 记录当前客户端对 k1、k2 的版本标记(底层是每个 key 维护一个修改计数器,key 每次被修改计数器 + 1)。MULTI开启事务,命令入队。EXEC提交时:Redis 检查被 WATCH 的 key,如果任意 key 的计数器和 WATCH 时不一致(被别的客户端改了),直接放弃执行整个事务,返回nil。如果没有被修改,正常执行队列命令。
WATCH 作用范围:只在当前连接生效;
EXEC/DISCARD之后会自动 UNWATCH。 缺点:WATCH 只能监控 key 是否被修改,不能做字段级监控(hash 只能监控整个 hash key)。
四、Redis 事务的特性总结(对比 ACID)
✅ 原子性(部分满足): 命令队列要么全部执行(无 WATCH 冲突),要么全部不执行;但是执行中途出错,已经成功的命令不会回滚。和数据库原子性不一样。
✅ 隔离性: 事务队列执行期间,其他客户端命令无法插入,天然隔离。
❌ 一致性: 不支持回滚,程序逻辑错误会导致数据不一致,需要业务层处理。
❌ 持久性: Redis 默认内存,RDB/AOF 只是持久化策略,事务执行成功后宕机依然可能丢数据,不保证持久。
五、Redis 事务底层实现原理
每个客户端对象
client里有:flags:标记是否处于事务状态CLIENT_MULTImultiState:事务状态结构体,里面存放命令队列数组,保存排队的命令和参数。
MULTI:修改client->flags |= CLIENT_MULTI,初始化队列。收到普通命令:判断是事务状态,调用
queueMultiCommand压入队列。EXEC:先检查 WATCH 监控的 key 是否被改动;
校验通过,遍历队列,逐个调用命令函数执行;
收集返回结果;执行完清空事务队列,清除
CLIENT_MULTI标记。
DISCARD:直接清空 multiState 队列,清除事务标记。
Redis 事务本质:客户端侧的命令缓冲队列 + 服务端串行批量执行 + WATCH 版本校验,不是服务端全局事务。
六、局限性 & 替代方案
不支持回滚;
事务内不能分支逻辑(if 判断),所有命令必须预先写好入队;
WATCH 有性能问题,高并发竞争场景容易事务反复失败;
不支持分布式事务。
替代方案
Lua 脚本(推荐): Redis 执行 Lua 脚本时,整个脚本是原子执行,中间不会插入其他命令;脚本内可以写判断逻辑;出错可以自己控制,是 Redis 实现原子操作首选。
Redis 7.0+ Redis Functions:和 Lua 类似,持久化函数。
分布式场景:用 Redlock / 业务层补偿,Redis 原生事务无法跨节点保证原子。
七、Lua 和 MULTI+WATCH 的区别
表格