Redis Watch 命令(超详细)

Redis Watch 命令详解:原子操作背后的“监视器”

在分布式系统中,多个客户端同时操作同一个数据,很容易引发“脏读”或“数据不一致”问题。就像几个人同时修改一份共享文档,谁先保存谁的版本就生效,容易造成覆盖。Redis 为了解决这类问题,提供了 Redis Watch 命令,它是一种实现乐观锁机制的核心工具。

你可能已经用过 SETGETINCR 这些基础命令,但当你需要在复杂场景下确保“操作前数据未被别人修改”,就该引入 WATCH 了。它就像一个“监视器”,在事务开始前盯住某个键,一旦该键被其他客户端修改,当前事务就会自动放弃执行。


Redis Watch 命令的基本原理

WATCH 命令的作用是“监视”一个或多个键。当客户端执行 WATCH 后,Redis 会记录这些键的当前状态。随后如果客户端使用 MULTI 开启事务,Redis 会在执行 EXEC 时检查这些键是否被其他客户端修改过。如果被改过,整个事务就失败,返回 nil,表示“这次操作不执行”。

这背后是典型的乐观锁思想:先假设不会冲突,只有在提交时才检查是否真的冲突。

举个生活中的例子:你去图书馆借一本书,发现它被别人占着。你没有立刻去等,而是告诉管理员“我先记下这本书的编号,等它回来我就马上借”。这就是“监视”——你并不阻塞,只是在等,一旦书被还回去,你就立刻去借。这比直接“占座”更高效。


如何使用 Redis Watch 命令?

基本语法与执行流程

WATCH key1 [key2 ...]
MULTI
EXEC

关键点在于:WATCH 必须在 MULTI 之前执行。如果 WATCH 之后,键被其他客户端修改,EXEC 会返回 nil,表示事务失败。


实际案例:模拟账户转账

假设我们有两个账户:account:aliceaccount:bob,初始余额均为 100 元。我们要实现一个“转账 50 元”的原子操作。

步骤一:初始化数据

SET account:alice 100
SET account:bob 100

步骤二:开启事务并监视账户

WATCH account:alice account:bob

MULTI

DECRBY account:alice 50

INCRBY account:bob 50

EXEC

如果在 EXEC 执行前,没有其他客户端修改 account:aliceaccount:bob,那么事务成功执行,返回结果为数组,包含两个命令的返回值。

步骤三:事务失败的场景

现在我们模拟一个并发冲突:在 WATCH 之后、EXEC 之前,另一个客户端修改了 account:alice

SET account:alice 90

此时客户端 A 再执行 EXEC,Redis 会检测到 account:alice 被修改过,于是返回:

(nil)

表示事务失败。客户端 A 需要重新执行整个流程,包括重新 WATCHMULTIEXEC

这就是 Redis Watch 命令 的“自我保护”机制:它不阻止别人修改,但会提醒你“你监视的资源变了,别再操作了”。


Watch 命令的注意事项

Watch 是“乐观”的,不是“悲观”的

与数据库中的行锁(悲观锁)不同,WATCH 不会阻止其他客户端访问键。它只是“观察”,在提交时才检查。因此性能更高,适合高并发读多写少的场景。

Watch 的作用范围是客户端会话

每个客户端独立地 WATCH 键。如果客户端 A 监视了 key1,客户端 B 也监视了 key1,它们互不影响。每个客户端的事务只关心自己监视的键是否被外部修改。

Watch 会自动取消

一旦 EXEC 成功执行,或者事务被 DISCARD 取消,Redis 会自动释放该客户端所有 WATCH 的键。也就是说,你不需要手动 UNWATCH,除非你想提前放弃监视。

但如果你在 WATCH 之后执行了 DISCARD,那 WATCH 会自动失效。这是设计上的安全机制。

Watch 可以监视多个键

你可以在一条命令中监视多个键,比如:

WATCH user:1001 user:1002 user:1003

这在处理关联数据时非常有用,比如用户信息和订单信息同时变更时。


Watch 与事务:配合使用的完整流程

我们来梳理一个完整的事务流程,包括 WATCHMULTIEXECDISCARD

WATCH stock:iphone stock:macbook

MULTI

DECR stock:iphone

INCR stock:macbook

EXEC

如果 EXEC 返回一个数组(如 ["1", "2"]),说明成功。如果返回 (nil),说明有冲突,必须重试。


常见错误与解决方案

错误一:WATCH 在 MULTI 之后执行

MULTI
WATCH key1 key2  # ❌ 错误:WATCH 必须在 MULTI 之前

Redis 会报错:WATCH inside MULTI is not allowed

✅ 正确做法:

WATCH key1 key2
MULTI
EXEC

错误二:在事务中执行了非事务命令

MULTIEXEC 之间,只能执行 SETGETINCR 等命令。不能执行 WATCHUNWATCHDISCARD 等控制命令。

✅ 正确顺序:

WATCH key1
MULTI
SET key1 "value"
EXEC

错误三:忽略事务失败,不重试

EXEC 返回 nil,说明事务失败。很多初学者会忽略这个结果,直接继续执行后续逻辑,导致数据不一致。

✅ 正确做法:在代码中检测 EXEC 的返回值,如果是 nil,就重新执行整个流程,直到成功。

import redis

r = redis.Redis(host='localhost', port=6379, db=0)

while True:
    try:
        r.watch('account:alice', 'account:bob')
        # 获取当前余额
        alice_balance = int(r.get('account:alice') or 0)
        bob_balance = int(r.get('account:bob') or 0)

        # 检查是否足够转账
        if alice_balance < 50:
            r.unwatch()
            print("余额不足,退出")
            break

        # 开启事务
        pipe = r.pipeline()
        pipe.decrby('account:alice', 50)
        pipe.incrby('account:bob', 50)
        result = pipe.execute()

        # 如果 result 为 None,说明事务失败,重试
        if result is None:
            continue  # 重新开始循环,重新 WATCH
        else:
            print("转账成功")
            break
    except Exception as e:
        print(f"发生异常:{e}")
        break

这个例子展示了如何在代码中正确处理 WATCH 事务失败的情况。


Watch 命令的适用场景

场景 是否适合使用 Watch
多个客户端同时修改同一份配置文件 ✅ 适合
账户余额增减(如转账) ✅ 强烈推荐
库存扣减(如电商下单) ✅ 推荐
读多写少的缓存更新 ✅ 适合
高频短事务(如点赞) ⚠️ 谨慎使用,冲突率高时性能下降
事务中包含复杂逻辑或耗时操作 ❌ 不推荐,应避免长时间持有 WATCH

总结与最佳实践

Redis Watch 命令 是实现分布式原子操作的关键工具,尤其适合需要“读-改-写”三步操作的场景。它通过乐观锁机制,在不阻塞其他客户端的前提下,保证了数据的一致性。

最佳实践建议:

  1. Always watch before multi:确保 WATCHMULTI 之前。
  2. Handle EXEC return value:检测 EXEC 是否返回 nil,失败则重试。
  3. Keep transaction small:事务越短,冲突概率越低。
  4. Don’t watch unnecessary keys:只监视真正相关的键,避免过度监视。
  5. Use retry mechanism in code:在应用层实现自动重试逻辑。

Redis Watch 命令虽然看似简单,但掌握它,你就拥有了在高并发环境下安全操作共享数据的能力。它不是“万能锁”,但却是构建可靠分布式系统的基石之一。当你下次需要“安全地修改多个键”时,别忘了这个默默守护你的“监视器”。