当前位置:首页 > 文章列表 > 数据库 > Redis > Redis事务中WATCH实现乐观锁

Redis事务中WATCH实现乐观锁

来源:17golang原创 2026-11-15 11:20:35 0浏览 收藏

Redis 事务里的 WATCH 适合解决“先读库存,再决定是否扣减”这类并发更新问题。它不会把其他客户端挡在门外,而是把 EXEC 变成有条件的提交:被监视的键在读取后到提交前发生变化,事务就放弃整组排队命令,应用重新读取并重试。

官方地址:https://redis.io/

下面用一个很小的 Go 库存扣减项目串起概念。示例依赖 go-redis/v9,重点是理解竞争失败如何返回,而不是把 WATCH 当成普通互斥锁。

先把 WATCH 的边界说清楚

Redis 事务通常由 MULTI、命令队列和 EXEC 组成。进入 MULTI 后,后续命令先排队,直到 EXEC 才执行。WATCH 位于读取和提交之间,监视一个或多个键;如果其中任意键在这段窗口内被修改,EXEC 会返回空结果,队列中的写入不会执行。

这就是乐观锁:默认冲突不频繁,先读取、准备写入,提交时再检查前提是否仍然成立。它不是阻塞式锁,也没有通用回滚机制;一个事务中某条命令执行报错时,其他已排队命令并不会自动回滚。

客户端读取、WATCH键、MULTI队列和EXEC条件与Redis键空间的静态关系说明图
图1:静态结构说明图,查看 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 只属于前者,有限重试后仍失败就应该返回明确的“竞争过高”;连接超时、键类型错误和库存不足则直接保留原错误语义。

库存键、事务回调、TxFailedErr、有限重试与连接错误业务错误的静态边界图
图2:静态边界说明图,查看竞争失败重试与其他错误返回的分工。

还要注意三个边界:第一,Redis 文档说明 6.0.9 及以后,被 WATCH 的键因过期或淘汰而变化也可能让事务中止;第二,Redis Cluster 中 WATCH 涉及的键需要满足同槽约束,多键操作可使用相同 hash tag;第三,如果逻辑越来越复杂,Lua 脚本通常更适合把读取和写入放在 Redis 端一次完成。

项目验收与常见疑问

验收时只需要确认契约:库存键是字符串数字;成功路径返回 nil 并扣减一次;竞争路径识别 TxFailedErr 后重新读取;超过上限返回明确错误;其他错误不被包装成“重试成功”。

WATCH 会锁住 Redis 吗?不会。它记录要检查的键,其他客户端仍可访问;冲突在 EXEC 时体现为本次提交失败。

为什么不直接用 DECRBY?如果只做无条件扣减,DECRBY 足够;当扣减前还要检查库存、状态或另一个键时,才需要 WATCH、Lua 或其他原子设计。

记住一句话:WATCH 保护的不是整个业务流程,而是“读取到的前提仍然成立”这一小段条件窗口。把冲突当作正常分支、给重试设上限,才能让 Redis 乐观锁真正适合业务代码。

版本声明
本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
Redis HSTRLEN返回0时怎样区分空值与字段不存在Redis HSTRLEN返回0时怎样区分空值与字段不存在
上一篇
Redis HSTRLEN返回0时怎样区分空值与字段不存在
Redis Lua脚本与Functions如何选择
下一篇
Redis Lua脚本与Functions如何选择
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之JavaScript设计模式
    前端进阶之JavaScript设计模式
    设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
    543次学习
  • GO语言核心编程课程
    GO语言核心编程课程
    本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
    516次学习
  • 简单聊聊mysql8与网络通信
    简单聊聊mysql8与网络通信
    如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
    500次学习
  • JavaScript正则表达式基础与实战
    JavaScript正则表达式基础与实战
    在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
    487次学习
  • 从零制作响应式网站—Grid布局
    从零制作响应式网站—Grid布局
    本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
    485次学习
查看更多
AI推荐
  • PubMedQA数据集详解:生物医学问答基准、功能与应用指南
    PubMedQA
    深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
    1036次使用
  • H2O EvalGPT:开源LLM大模型评估与排行榜工具
    H2O EvalGPT
    H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
    1034次使用
  • LMArena是什么?伯克利AI模型评估平台使用指南与功能解析
    LMArena
    LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
    1178次使用
  • 斯坦福HELM:大语言模型Holistic Evaluation整体评估框架详解
    HELM
    深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
    1139次使用
  • MMBench详解:多模态大模型基准测试、功能特点与使用指南
    MMBench
    MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
    981次使用