Redis WATCH 怎样在并发修改时放弃事务
来源:17golang原创
时间:2026-10-18 10:56:31 486浏览 收藏
Redis 的 WATCH 让 EXEC 带上条件:从 WATCH 建立监视到 EXEC 检查时,只要任一被监视键发生了会使监视失效的修改,EXEC 就返回空回复,整组排队命令都不执行。这不是执行失败后回滚,而是在执行前放弃本次事务。
如果我要根据读到的旧配置计算一个新配置,我会先考虑 WATCH:竞争较少时,允许别人先修改,自己发现冲突后重读即可。但如果只是计数加一,我会直接选 INCR,而不是为了“用了事务”增加往返与重试。本文比较的是这类取舍,不声称发生过某次实际线上事故。
官方地址:https://redis.io/docs/latest/commands/watch/。以下双连接示例供读者在隔离测试实例操作,本文未执行命令,结果描述均为预期。
我会先确认是冲突,还是命令错误
日志里出现“事务没成功”时,我不会立即增加重试次数,而会先分清 EXEC 的返回类型:
| 观察到的结果 | 含义 | 处理方向 |
|---|---|---|
| EXEC 空回复 | WATCH 条件失效,本次队列未执行 | 重新读取并计算,按预算重试 |
| EXEC 返回数组,某个元素是错误 | 进入执行阶段,某条命令报错 | 逐项处理,不能假定其他写入撤销 |
| EXECABORT 等命令级错误 | 例如入队阶段错误导致事务被拒绝 | 修正命令或环境,不当普通竞争重试 |
| 超时或连接断开,没有确定回复 | 客户端不知道最终结果 | 不能直接归类为 WATCH 冲突 |
空回复也不等于“成功执行了零条命令”的空数组。客户端可能把冲突表示为专门异常或状态,应用应遵循所用驱动的返回约定,而非用列表是否为空或日志是否有文字判断。
用两个持久连接看清中止条件
读者需要两个长期保持的客户端连接 A、B,连接到同一测试实例和数据库。在两个现有命令会话中分别进入 redis-cli:
# 在两个独立的命令会话中各执行一次,后续命令在各自 redis-cli 内输入 # 仅连接隔离测试实例;不要在业务实例上初始化示例键 redis-cli
确认专用样例键可用于测试后,按下表人工交错输入。表中“暂停”是操作提示,不是 Redis 命令;不要把两列一次粘贴到同一连接。
| 操作位置 | 输入或动作 | 预期观察 |
|---|---|---|
| B:准备数据 | SET article:watch:config v1 | 设置已知初值 |
| A:建立监视 | WATCH article:watch:config | OK,只表示建立监视 |
| A:读取输入 | GET article:watch:config,随后暂停 | 读到 v1 |
| B:并发改写 | SET article:watch:config v2 | B 可以正常写入,不被 WATCH 阻塞 |
| A:准备事务 | MULTI,再输入 SET article:watch:config v3 | 分别回复 OK、QUEUED |
| A:提交 | EXEC | 空回复,不会执行这次 SET |
| A:教学观察 | GET article:watch:config | 没有其他写入时仍为 v2 |
关键不在于 v1、v2 是否不同,而在于监视期间是否发生修改。WATCH 不是在最后把两个字符串做一次相等比较。QUEUED 也只表示命令已入队,不能当成更新成功。
这个结构中,连接 A 持有监视状态和事务队列,二者都关联同一个被监视键;连接 B 的写命令是外部修改来源,EXEC 是条件提交的入口。图中的连线只表达这些归属和依赖。

连接边界比重试次数更重要
我会先检查 WATCH、读取、MULTI、EXEC 是否由同一条连接完成。逐次运行独立的 redis-cli WATCH ...、redis-cli GET ... 并不能拼成一次事务:前一条进程退出,监视连接也结束了。
应用连接池同样需要显式事务会话或连接绑定,不能把这几条调用随意分配给不同连接,也不能让其他请求共享处于监视或事务状态的连接。若更新依赖多个键,应在读取这些依赖之前监视所有相关键;只监视最终写入键,不能保护遗漏的读取条件。
不继续操作且尚未进入 MULTI 时,用 UNWATCH 清除监视;已经进入 MULTI 而决定放弃,用 DISCARD 清空队列并结束事务,同时取消监视。不要在排队后用 UNWATCH 充当事务取消命令。EXEC 无论成功还是因冲突放弃,都会清除监视;连接关闭也会清除。
连接归还池之前,应由驱动完成明确清理;若协议状态已不确定,我会淘汰该连接,不把“可能还在事务里”的连接交给下一个请求。
冲突后重读重算,不复用旧写入
收到确定的 WATCH 冲突后,我会从新的 WATCH 开始,重新读取当前值,再重新计算和排队。只把第一次算出的 v3 再提交一次,虽然可能不再冲突,却已失去“基于当前输入计算”的意义。
清理状态、重读重算、重试预算应是三个独立约束:设置尝试次数或截止时间,竞争持续时加入有界退避,超过预算就把冲突交给调用方。重试循环内不要提前发送邮件、扣外部账户或调用不可撤销服务,否则 Redis 队列虽未执行,外部副作用却可能重复发生。
网络超时需要另一条处理路径:如果 EXEC 已送达但回复丢失,事务可能已经执行。它不是确定的空回复,盲目重跑非幂等操作可能重复更新。应结合业务标识和结果确认机制处理,而不是统一捕获所有错误后继续循环。

WATCH 不负责执行错误回滚
我不会把 WATCH 介绍成数据库式的“出错全部撤销”。如果监视条件通过,EXEC 开始执行,某条命令因类型不符报错,其他排队命令仍会被处理。此时应检查回复数组的每一项,而非只检查 EXEC 调用有没有返回。
另一个边界是过期:Redis 自身的过期、淘汰也可能使监视失效。Redis 6.0.9 之前,被监视键过期不导致事务中止,这一历史差异不能忽略。测试若带有效期,应记录版本与 TTL,不要把服务器自身修改误判成“莫名其妙的并发写入”。
WATCH 监视的不是一个只允许别的客户端触发的锁。监视客户端在事务之外对键的修改也可能使自己的监视失效;MULTI 中的命令在 EXEC 前只是排队,不能与立即写入混为一谈。
按业务约束选择,不把 WATCH 当互斥锁
| 需求 | 我会优先考虑 | 需要接受的代价 |
|---|---|---|
| 单纯原子加一 | INCR | 使用命令已有的整数语义 |
| 应用基于读取结果计算再更新 | WATCH 配合同连接事务 | 竞争时重读重算,有重试预算 |
| 固定一组命令,无条件读取依赖 | MULTI / EXEC | 仍要处理每条命令错误,不承诺回滚 |
| 禁止其他写入者进入业务临界区 | 另行设计协调机制 | WATCH 不会阻塞对方写入 |
别人修改后又改回原值,还会中止吗?
会发生监视失效;它检测监视期间的修改,而不是只比较最终内容。需要值相等语义时,不应把 WATCH 当成单纯的字符串比较函数。
只 WATCH 没有写入的键有意义吗?
有。只要那个键影响更新决策,就应成为读取依赖的一部分。被监视键不要求一定出现在事务的写命令中。
冲突后直接再 EXEC 可以吗?
不行。本次事务已经结束,监视也已清除。应重新监视、读取、计算和建立事务,而不是重复发 EXEC 或复用旧计算结果。
我的选择条件是:能固定同一连接、读取依赖清楚、竞争可接受且能重算时,WATCH 才是合适的乐观并发控制;无法满足这些条件时,增加重试次数不会修复模型本身。
官方参考:事务语义 https://redis.io/docs/latest/develop/using-commands/transactions/;EXEC 返回类型 https://redis.io/docs/latest/commands/exec/;取消排队 https://redis.io/docs/latest/commands/discard/;取消监视 https://redis.io/docs/latest/commands/unwatch/。
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习