redis的watch命令通过乐观锁实现并发安全,先监视key再校验是否被修改,若任一key在watch后、exec前被改则事务失败返回nil,适用于读多写少场景且需客户端重试。

Redis 中的 WATCH 命令是实现乐观锁的核心机制,它不加锁、不阻塞,而是通过“先观察、再校验”的方式保障并发写入的安全性。
WATCH 的作用原理
WATCH 会在事务执行前对一个或多个 key 建立监视状态。一旦这些 key 在 WATCH 后、EXEC 前被其他客户端修改(哪怕只是 INCR、SET 或 DEL),整个事务就会被拒绝执行,EXEC 返回 nil。这本质上是一种基于状态比对的 CAS(Compare-And-Swap)行为:Redis 记录了被监视 key 的当前版本(内部使用逻辑时钟或变更标记),提交时检查是否一致。
标准使用流程
- 先执行
WATCH key1 [key2...],指定需要保护的数据项 - 读取当前值(如
GET key1),用于后续业务逻辑计算 - 调用
MULTI开启事务 - 将更新操作(如
INCRBY key1 10、SET key2 "new")加入队列 - 执行
EXEC—— 此时 Redis 自动校验所有被WATCH的 key 是否未变;若任一 key 被改过,事务整体失败,返回nil
关键注意事项
WATCH 生命周期很短:监视只在当前连接中有效,且仅持续到 EXEC 或 DISCARD 执行完毕(无论成功与否,监视都会自动取消)。不需要手动 UNWATCH,除非想提前解除监视。
不解决 ABA 问题:如果 key 值被改成其他值又改回原值(如从 "100" → "90" → "100"),WATCH 无法感知中间变更,仍会允许事务提交。业务敏感场景建议配合版本号字段(如 balance_v2 + version)来规避。
仅监控 key 级别变化:不管修改来自哪个客户端、用什么命令(SET、INCR、EXPIRE 等),只要值或存在性变了就算触发失效。但注意:key 过期本身不会导致事务失败(过期后 key 不存在,等效于被删,此时 EXEC 仍会失败)。
实际应用建议
- 适合读多写少、冲突概率低的场景,比如用户积分更新、订单状态轻量变更
- 客户端必须处理
EXEC返回nil的情况,通常需重试(可加指数退避,避免雪崩) - 避免 WATCH 过多 key,否则校验开销上升,失败率提高
- 在 Go/Java 等客户端中,推荐封装为带重试的原子操作函数(如 go-redis 的
Watch方法),而非裸写命令流











