Redis事务中WATCH实现乐观锁
来源:17golang原创
时间:2026-11-15 11:20:35 184浏览 收藏
Redis 事务里的 WATCH 适合解决“先读库存,再决定是否扣减”这类并发更新问题。它不会把其他客户端挡在门外,而是把 EXEC 变成有条件的提交:被监视的键在读取后到提交前发生变化,事务就放弃整组排队命令,应用重新读取并重试。
官方地址:https://redis.io/
下面用一个很小的 Go 库存扣减项目串起概念。示例依赖 go-redis/v9,重点是理解竞争失败如何返回,而不是把 WATCH 当成普通互斥锁。
先把 WATCH 的边界说清楚
Redis 事务通常由 MULTI、命令队列和 EXEC 组成。进入 MULTI 后,后续命令先排队,直到 EXEC 才执行。WATCH 位于读取和提交之间,监视一个或多个键;如果其中任意键在这段窗口内被修改,EXEC 会返回空结果,队列中的写入不会执行。
这就是乐观锁:默认冲突不频繁,先读取、准备写入,提交时再检查前提是否仍然成立。它不是阻塞式锁,也没有通用回滚机制;一个事务中某条命令执行报错时,其他已排队命令并不会自动回滚。

准备一个可重试的库存扣减项目
先把库存放在一个字符串键里,例如 stock:sku-100。每次扣减都必须满足两个条件:读取到的库存仍然是刚才看到的版本,并且库存大于等于购买数量。重试次数要有上限,否则高冲突时请求会无限占用连接。
# 初始化一个演示库存键;真实环境请替换为受控的业务键 redis-cli SET stock:sku-100 10 # 安装 Redis 的 Go 客户端;go.mod 会记录依赖版本 go get github.com/redis/go-redis/v9
项目的关键不是“把所有命令塞进 MULTI”,而是先在 WATCH 保护的连接上读出当前值,再根据这个值构造事务。读操作不能放在 MULTI 后期待立即得到结果,因为那时它只会返回 QUEUED。
用 go-redis 把读取和提交连起来
go-redis 的 Watch 回调会把事务对象交给我们。回调里先用 tx.Get 读取库存,再用 TxPipelined 排队写入;如果监视键发生变化,客户端会把这次竞争报告为 redis.TxFailedErr。
package main
import (
"context"
"errors"
"fmt"
"github.com/redis/go-redis/v9"
)
// deductStock 只在读取到的库存仍未变化时提交扣减。
func deductStock(ctx context.Context, rdb *redis.Client, key string, quantity int64) error {
const maxRetries = 5 // 冲突时最多重试,避免高并发下无限占用连接
for attempt := 0; attempt
这里的 TxFailedErr 代表“本次乐观锁前提失效”,不是 Redis 宕机,也不是库存不足。把这三类情况分开,调用方才能分别决定重试、提示用户或触发故障处理。
把竞争失败和真正错误分开处理
可以把一次调用想成两个边界:左侧是业务根据库存值准备写入的竞争域,右侧是连接、数据类型和业务规则组成的错误域。TxFailedErr 只属于前者,有限重试后仍失败就应该返回明确的“竞争过高”;连接超时、键类型错误和库存不足则直接保留原错误语义。

还要注意三个边界:第一,Redis 文档说明 6.0.9 及以后,被 WATCH 的键因过期或淘汰而变化也可能让事务中止;第二,Redis Cluster 中 WATCH 涉及的键需要满足同槽约束,多键操作可使用相同 hash tag;第三,如果逻辑越来越复杂,Lua 脚本通常更适合把读取和写入放在 Redis 端一次完成。
项目验收与常见疑问
验收时只需要确认契约:库存键是字符串数字;成功路径返回 nil 并扣减一次;竞争路径识别 TxFailedErr 后重新读取;超过上限返回明确错误;其他错误不被包装成“重试成功”。
WATCH 会锁住 Redis 吗?不会。它记录要检查的键,其他客户端仍可访问;冲突在 EXEC 时体现为本次提交失败。
为什么不直接用 DECRBY?如果只做无条件扣减,DECRBY 足够;当扣减前还要检查库存、状态或另一个键时,才需要 WATCH、Lua 或其他原子设计。
记住一句话:WATCH 保护的不是整个业务流程,而是“读取到的前提仍然成立”这一小段条件窗口。把冲突当作正常分支、给重试设上限,才能让 Redis 乐观锁真正适合业务代码。
-
311 收藏
-
483 收藏
-
379 收藏
-
172 收藏
-
464 收藏
-
239 收藏
-
331 收藏
-
421 收藏
-
209 收藏
-
406 收藏
-
135 收藏
-
401 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习