redis事务中exec前所有命令只是入队不执行:multi到exec间命令仅压入客户端队列,get等读操作查的是外部当前状态而非队列待写值,exec后才原子串行执行并返回结果。

Redis事务中EXEC前所有命令只是入队,不执行也不返回结果
这是Redis事务最核心的行为特征:从MULTI到EXEC之间的命令不会真正执行,只是被压入客户端连接的命令队列。此时读操作(如GET)不会访问实际数据,自然读不到“预期中的新值”。这不是bug,是设计使然——Redis事务不提供隔离性,也不做预执行校验。
常见错误现象:
- 在MULTI后立即GET刚SET的key,返回null或旧值
- 用WATCH监听key后,在EXEC前反复GET观察状态,发现值没变
-
QUEUED是唯一返回值,代表入队成功,不是执行结果 - 事务内命令的执行时机严格限定在
EXEC之后,且是原子性串行执行 - 没有“事务内快照”机制,
GET在EXEC前查的是外部当前状态,不是队列里的待写入值
为什么不能在EXEC前读取事务内写入的值
因为Redis服务端根本没做任何状态变更。整个事务块对数据层完全透明,直到EXEC到来才批量取出队列、依次执行、逐个返回结果。这和数据库的“事务内可见性”完全不同——Redis事务本质是命令打包+串行化调度,不是ACID事务。
- 事务内
SET的key,只有EXEC后才会真正写入内存 - 若想提前验证逻辑,必须把检查逻辑放在
MULTI之前,或用WATCH+重试机制兜底 -
WATCH只影响EXEC是否失败,不影响队列内命令的执行顺序或可见性
GET在事务内外的行为差异
同一个GET命令,在事务外直接返回当前值;在事务内只是入队,EXEC时才执行并返回当时的真实值——但这个“当时”,已是所有前置命令(包括同事务内的SET)执行完之后。
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
127.0.0.1:6379> SET foo bar OK 127.0.0.1:6379> MULTI OK 127.0.0.1:6379> SET foo baz QUEUED 127.0.0.1:6379> GET foo QUEUED 127.0.0.1:6379> EXEC 1) OK 2) "baz" ← 注意:这里才返回,且是SET之后的值
- 事务内
GET永远无法读到同事务内尚未EXEC的SET结果,哪怕它们在队列里相邻 - 如果需要“先读后写”的原子逻辑,应改用
EVAL脚本,由Lua在服务端一次性完成 - 客户端不能依赖事务内中间状态做分支判断,所有条件逻辑必须前置或后置
替代方案:什么时候该放弃MULTI/EXEC改用EVAL
当你需要在一次请求中完成“读—判断—写”闭环,且要求中间状态不可见、不可打断,MULTI/EXEC就无能为力。这时EVAL是更直接的选择——Lua脚本在服务端原子执行,期间其他客户端无法插入操作。
-
EVAL "return redis.call('GET', KEYS[1])" 1 foo可以立即拿到当前值 - 配合
redis.call('SET', ...)就能实现真正的读写耦合逻辑 - 注意Lua脚本不能含阻塞操作(如
SLEEP),也不能调用非确定性命令(如TIME) - 脚本过长会影响Redis单线程性能,简单逻辑优先用原生命令组合
事务的“队列延迟执行”特性常被误当成“弱一致性”,其实它连一致性模型都不参与——它只是个命令管道。真正需要状态协同的地方,得靠WATCH重试或EVAL接管。










