Redis中WATCH失效的根本原因是SessionCallback不支持WATCH-EXEC语义,因其未保证WATCH、MULTI、EXEC在同连接同会话中连续执行;正确做法是手动用RedisConnection原生命令顺序调用watch/multi/操作,或改用原子Lua脚本替代。

WATCH在Redis事务中为什么突然不生效
根本原因不是WATCH写错了,而是你用的SessionCallback默认不支持WATCH–EXEC语义。Spring Data Redis的SessionCallback本质是把一连串命令发给Redis,但不会自动包裹MULTI/EXEC,更不会帮你维护WATCH的连接上下文。WATCH必须和后续的MULTI、EXEC在同一个连接、同一次会话中执行,而SessionCallback内部可能复用连接或拆分执行,导致WATCH被提前丢弃。
用SessionCallback正确实现带WATCH的事务
关键不是“能不能用”,而是“怎么用才不破坏WATCH生命周期”。必须手动控制命令顺序和连接绑定:
-
SessionCallback里不能只写watch(key),必须紧跟着multi()和后续操作,三者在同一个RedisOperations实例上调用 - 避免在
SessionCallback里混用opsForValue().set()这类高层API——它们会绕过当前事务,直接发命令,导致WATCH失效 - 所有读写操作都得用
RedisConnection原生命令:比如connection.watch(key.getBytes())、connection.multi()、connection.set(key.getBytes(), value.getBytes()) - 别依赖
execute(SessionCallback)的返回值做条件判断后再执行事务——WATCH一旦发出,就必须立刻进MULTI,中间不能有其他Redis交互
更稳妥的替代方案:不用SessionCallback
如果你只是想原子性地检查再更新,WATCH+事务反而增加复杂度。多数场景下,直接用Lua脚本更可靠:
由 ReMe 驱动的记忆管理系统,支持跨会话记忆持久化、自动应用用户偏好以及智能上下文压缩...
- Lua在Redis服务端原子执行,天然规避并发竞争,不需要WATCH
- 例如用
eval执行一段脚本:if redis.call("get", KEYS[1]) == ARGV[1] then redis.call("set", KEYS[1], ARGV[2]) return 1 else return 0 end - Spring里调用
redisTemplate.execute(new DefaultRedisScript(script, Integer.class), keys, args) - 注意Lua里KEYS和ARGV必须显式传入,不能在脚本里硬编码key名,否则无法被Redis Cluster路由
调试WATCH是否真生效的实操方法
光看代码逻辑没用,得验证Redis底层行为:
- 开启Redis慢日志或用
redis-cli --monitor抓真实命令流,确认WATCH后是否紧跟着MULTI,中间有没有穿插其他命令(比如INFO、CONFIG等) - 在WATCH之后、EXEC之前,用另一个客户端手动改掉被watch的key,再执行EXEC——如果返回nil,说明WATCH生效;如果返回结果数组,说明已失效
- 检查Spring配置:确认
RedisTemplate没开启enableTransactionSupport = false(虽然它对SessionCallback实际无效,但开着容易误导) - 集群模式下,所有WATCH的key必须落在同一个slot,否则Redis会直接报错
CROSSSLOT Keys in request don't hash to the same slot
WATCH不是开关,是连接状态。它一旦发出,就绑定当前连接的生命期;断连、重连、连接池回收,都会让它作废——这点最容易被忽略。










