恒美微站 Logo 恒美微站
  • 首页
  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心
  • 联系我们

Redis事务机制详解:MULTI/EXEC/WATCH与回滚误区

  • 首页
  • 资讯中心
  • /
  • Redis事务机制详解:MULTI/EXEC/WATCH与回滚误区

相关资讯

AI深度思考过程导出完整指南:从页面操作到API脚本 2026/10/15 5:35:20
S7-1200 PUT/GET通信全解析:从组态到避坑的实战指南 2026/10/15 5:35:20
M580与ATV930通过Modbus TCP实现以太网通讯实验 2026/10/15 5:35:20

最新资讯

基于Python Django的图书商城抽奖与积分兑换系统实战设计
Spring Boot校园一卡通系统毕业设计实战:从PPT到本地跑通
AI技能库设计实战:从提示词工程到可复用能力单元
Python 数据类型详解:从数字到序列的完整入门
用NVIDIA Megatron Core实现大规模确定性预训练
上下文驱动模式切换:一种被低估的架构设计范式

今日推荐

AI视频生成的时间一致性难题与时空建模演进
基于bbox的PDF文档解析:多栏重排与水印过滤实战
探矿数据进RAG前的清洗全攻略:TXT、Word、PDF、网页四类文档处理实践

本周热门

UE动画修改实战:从资产编辑到重定向与蒙太奇驱动
统计随机数生成器攻击下的KLJN安全密钥交换协议Matlab仿真
政务API安全治理:资产测绘、低代码编排与行标对标实践

本月精选

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证
2026 大模型集体涨价:用 Python 做企业 Token 成本测算与选型避坑(附配置)

Redis事务机制详解:MULTI/EXEC/WATCH与回滚误区

发布时间:2026/10/15 5:35:20
Redis事务机制详解:MULTI/EXEC/WATCH与回滚误区 这系列写到第三十五篇终于轮到 Redis 事务Transaction了。我见过太多人第一次在 Redis 里敲下 MULTI 和 EXEC误以为它和 MySQL 里的事务差不多执行到一半失败了前面的操作自动回滚数据干干净净。真实情况是Redis 的事务里既没有回滚也不会因为中间一条命令报错就刹停这个反差让不少人在生产环境里吃了亏。这篇文章不打算只列文档而是把事务从概念、命令、错误处理到使用场景一条线讲清楚重点回答三个问题Redis 事务到底能保证什么、不能保证什么以及从 MULTI 到 EXEC 中间每一步发生了什么。适合刚接触 Redis 不久、想系统理解事务机制的开发者也适合已经在用事务但偶尔被并发问题坑到的同学。下面所有命令示例我都在本地 Redis 环境里跑过版本 7.0 左右不同版本对错误处理的表现略有差异文章里我会专门标注。1. 先把概念摆正Redis 事务不是你想的那种“全有或全无”1.1 事务到底解决了什么问题在没有事务的时候我们要连续执行几条命令比如扣库存、加积分、写日志。网络一旦抖动中途丢一条命令数据就处于中间态。事务的价值就是让这几条命令按顺序、不间断地被执行。Redis 是单线程处理命令的MULTI 开启后命令先入队EXEC 触发时 Redis 会一口气把队列里的命令执行完执行期间不会去处理其他客户端的命令。这就解决了“被穿插”的问题。1.2 拿 ACID 四个字逐条对照很多人拿 ACID 来衡量 Redis 事务实际上 Redis 事务在这四个维度上的表现是“偏科”的原子性Atomicity部分满足。如果命令在入队时全部合法EXEC 会逐个执行但执行过程中如果某个命令运行时出错Redis 并不会回滚已经成功的命令。一致性Consistency基本满足。事务执行前后Redis 数据结构层面的约束不会被破坏只是错误命令有自己的结果。隔离性Isolation最强的一项。由于单线程执行EXEC 触发后事务内的命令不会被其他客户端的命令穿插。持久性Durability不保证。即使开启 AOF也存在刷盘延迟完全没有持久化配置时重启数据可能全部丢失。这张表我经常在面试里拿来提问大家对比一下关系型数据库和自己手头的 Redis心里就有数了维度关系型数据库事务Redis 事务原子性失败回滚全有或全无入队错误整体拒绝运行时错误不回滚一致性约束完整事务前后状态合法不破坏数据结构但业务一致性要自己保证隔离性隔离级别控制单线程顺序执行天然互不穿插持久性依赖 redo/undo 日志依赖 RDB/AOF存在丢失窗口1.3 最容易被误解的一点Redis 故意不设计回滚为什么不做回滚主要原因有两点。第一Redis 命令本身执行速度极快事务中命令数量一般也不多回滚机制需要记录 undo 日志代价远大于收益。第二事务中导致回滚的运行时错误本质上是程序员的 bug比如给字符串键做 INCR这种错误在敲代码时就应该能避免而不是指望数据库兜底。所以 Redis 官方的设计哲学是你知道你在干什么错误由你自己负责。理解了这个哲学再看后面讲到的两类错误处理就不会觉得奇怪了。2. MULTI / EXEC / DISCARD从入队到执行完整走一遍事务流程2.1 MULTI进入“正在收集命令”状态在最简单的场景里一条 MULTI 命令就把客户端切换进事务状态。用 redis-cli 演示一下127.0.0.1:6379 MULTI OK 127.0.0.1:6379 SET balance 100 QUEUED 127.0.0.1:6379 INCRBY balance 50 QUEUED 127.0.0.1:6379 EXEC 1) OK 2) (integer) 150命令入队之后返回的并不是执行结果而是一个固定的 QUEUED 字符串。也就是说在 EXEC 执行前命令只是放在一个 FIFO 队列里。这里有两个新手容易踩的坑第一进入事务状态后如果连接断开事务状态会被清空队列里的命令全部丢失第二在事务状态下再发 MULTIRedis 会直接返回错误“MULTI calls can not be nested”所以封装底层客户端时要避免重复调用 MULTI。2.2 EXEC按入队顺序执行返回所有结果EXEC 做的事情很简单把队列里的命令按顺序执行然后把每条命令的返回结果打包成一个数组还给客户端。正因为命令是“入队先于执行”的所以在事务里没法这样做先 GET 一个值根据这个值决定要不要 SET。因为入队时结果还没出来等 EXEC 执行时所有命令都是一口气跑的中间插不进任何条件判断。这也是为什么 WATCH 和 Lua 脚本在 Redis 事务体系里如此重要。2.3 DISCARD事务还没执行时取消它如果 MULTI 之后发现命令不想执行了用 DISCARD 清空队列并退出事务状态。127.0.0.1:6379 MULTI OK 127.0.0.1:6379 SET foo bar QUEUED 127.0.0.1:6379 DISCARD OK 127.0.0.1:6379 GET foo (nil)注意DISCARD 只能在 EXEC 之前调用。它清空的只是“还没执行的事务队列”对已经通过 EXEC 执行完的命令没有任何回滚能力。这个点我后面会专门拿出来说因为它是我见过被误解最多的地方。2.4 事务三种状态的小结事务从开始到结束其实就是五个命令在反复切换状态阶段命令返回值做了什么事务前MULTIOK客户端进入事务状态事务中普通命令QUEUED命令入队不执行事务中EXEC数组按序执行队列里的命令事务中DISCARDOK清空队列退出事务事务前WATCHOK监视键必须在 MULTI 前执行这个表建议收藏因为很多客户端异常就是“某个连接还残留事务状态”导致的。判断一个连接处于什么状态先看它是刚执行完 MULTI还是已经 QUEUED 了很多命令再决定下一步动作。3. 错误处理的两条岔路入队报错与执行报错行为完全不同3.1 第一类错误入队阶段就报错如果命令本身有语法错误、参数个数不对或者命令根本不存在那么在 MULTI 后入队时 Redis 就会返回错误响应而不是 QUEUED。举个例子127.0.0.1:6379 MULTI OK 127.0.0.1:6379 SET foo bar QUEUED 127.0.0.1:6379 INCRBY foo (error) ERR wrong number of arguments for incrby command 127.0.0.1:6379 EXEC (error) EXECABORT Transaction discarded because of previous errors.注意最后 EXEC 的结果整个事务被丢弃之前已经入队的 SET foo bar 也不会执行。这个行为其实经历了一次演进老版本 Redis 可能会执行成功的那部分命令新版为了让大家少踩坑干脆在入队阶段只要出现任何错误EXEC 就直接拒绝。我在迁移老项目时专门排查过这个问题如果你的客户端依赖某个很老的服务端版本这处行为差异一定要验证。3.2 第二类错误入队合法执行时报错这类错误更隐蔽。命令本身是合法的只是在执行时因为数据类型、键状态等因素失败了。最典型的是对字符串键做 INCR127.0.0.1:6379 MULTI OK 127.0.0.1:6379 SET msg hello QUEUED 127.0.0.1:6379 INCR msg QUEUED 127.0.0.1:6379 SET done 1 QUEUED 127.0.0.1:6379 EXEC 1) OK 2) (error) WRONGTYPE Operation against a key holding the wrong kind of value 3) OK结果很有意思第一条 SET 成功第二条 INCR 报错第三条 SET 仍然成功。Redis 不会因为中间某条命令报错就停下来更不会去回滚前面的成功命令。这是 Redis 事务和关系型数据库事务最大的分水岭。如果你在业务里依赖“要么全部成功、要么全部失败”那就必须借助 Lua 脚本或者在事务内避免可能出现运行时错误的命令。3.3 为什么 Redis 选择不回滚前面提到过一部分设计哲学这里再补充一点细节。对入队错误的处理Redis 可以比较“激进”地整体拒绝因为这类错误在命令入队时就能被静态检查出来拦下来对性能影响可以忽略。但执行时错误需要真正的 undo 机制Redis 不想为这种运行时错误承担额外开销。换句话说命令的“合法性”由入队阶段保证命令的“正确性”由你自己保证。理解了这一点再看官方文档里那句“事务中的错误通常都是编程错误”就比较容易接受了。4. WATCH 乐观锁如何让事务在并发场景下安全落地4.1 WATCH 的作用在执行前先盯住关键键Redis 事务里没有“在事务内先读后写再判断”的能力那并发场景下的原子更新怎么办答案就是 WATCH 命令。WATCH 必须在 MULTI 之前执行它的作用相当于给指定的键加了一把乐观锁如果在你 WATCH 之后、EXEC 执行之前这些键被其他客户端修改了那么 EXEC 会放弃执行返回 nil。一个经典的库存扣减例子127.0.0.1:6379 WATCH stock:1001 OK 127.0.0.1:6379 GET stock:1001 (integer) 5 # 模拟并发客户端在这时执行了 DECR stock:1001把库存改成了 4 127.0.0.1:6379 MULTI OK 127.0.0.1:6379 DECR stock:1001 QUEUED 127.0.0.1:6379 EXEC (nil)EXEC 返回 nil说明 WATCH 的键在事务执行前被改过事务被安全放弃。这时候客户端应该重新读取库存再决定是否重试。如果返回的不是 nil而是结果数组说明事务正常执行。4.2 底层机制watched_keys 和 dirty 标志简单说一下 WATCH 是怎么实现的。服务端维护着一张 watched_keys 表键指向一组监视它的客户端。任何客户端对某个键执行写命令后服务端会把这些被监视的客户端标记为“脏”。EXEC 执行前会检查如果当前客户端被标记为脏直接放弃事务返回 nil。所以 WATCH 监视的是“这个键是否被改过”这个事实而不是“被改成了什么值”。它能防住并发修改但防不住两个客户端同时读到相同旧值后各自提交——这就是乐观锁的固有特点冲突要靠重试来解决。我再举个例子比如账户间转账WATCH 两个账户对应的键GET 出余额检查扣款账户余额充足后MULTI 里做 DECR 和 INCR。只要 EXEC 前任何一个账户被别的请求碰过整个转账就放弃客户端重新读余额再来一轮。这种模式在旧的秒杀、抢红包、库存锁场景里非常常见。4.3 UNWATCH、连接复用与常见误用如果 WATCH 之后不想执行事务了可以用 UNWATCH 取消全部监视或者直接 DISCARD 清空事务。还有两个细节容易踩坑。第一WATCH 必须在 MULTI 之前调用如果在 MULTI 之后调用 WATCHRedis 会直接报错。第二WATCH 监视的键不仅是“被 SET 修改”才会触发删除DEL、过期过期键自动删除、甚至某些会改变键内容的其他命令都会触发所以在做缓存淘汰、TTL 相关的业务时要特别小心。一个更隐蔽的问题是很多客户端框架会在连接池里复用连接如果你上一个请求用 WATCH 然后没有正确结束事务连接被归还后下一个请求可能会带上残留的 WATCH 状态。我一直建议在 finally 块里显式 UNWATCH 或 DISCARD避免连接复用时把脏状态带到下一个业务请求里。这个问题在 Go、Java 的 Redis 客户端里都出现过排查起来很费劲。5. 事务、Pipeline 与 Lua 脚本三兄弟不能混为一谈5.1 Pipeline 只解决性能问题不解决原子问题很多人觉得“这不都是把多条命令一次发出去吗”其实差别很大。Pipeline 是把一组命令一次性写给 Redis减少网络往返但 Redis 执行这些命令时并不保证不被其他客户端的命令穿插。也就是说Pipeline 训练的是网络效率事务和 Lua 脚本则能保证执行期间的隔离性。如果你的场景只在意性能Pipeline 就够如果你要求命令之间不能被穿插必须用事务或者 Lua。5.2 Lua 脚本原子性更强还能带条件判断Redis 执行 Lua 脚本时整个脚本是在单线程里一口气跑完的中途不会执行其他命令。更关键的是脚本内部可以用 redis.call 读取数据、做条件判断再决定下一步操作。比如前面那个库存扣减真正稳妥的做法可以这样local stock redis.call(GET, KEYS[1]) if tonumber(stock) and tonumber(stock) 0 then return redis.call(DECR, KEYS[1]) end return -1通过 EVAL 提交执行。这样既保证了原子又有了逻辑分支能力比 WATCH MULTI 的重试模型更直接。像 Redis 官方文档也提到很多时候 Lua 脚本可以替代事务因为它能在一个原子单元里完成读、判断、写不需要 WATCH 这类外部机制。5.3 不同场景下的选型建议场景推荐方案批量写、无逻辑分支Redis 事务 / Pipeline并发更新、需要先读后判断Lua 脚本需要乐观锁、可容忍重试WATCH 事务只求降低 RTTPipeline我个人的倾向很简单现代 Redis 版本里 Lua 脚本已经足够稳定只要脚本本身不复杂尽量用 Lua 顶替传统事务事务剩下的最大价值反而是那些“我就要用标准命令组合完成不想维护脚本”的运维场景。还有一点提醒Lua 脚本里如果发生运行时错误Redis 同样不会回滚脚本内已执行的操作只是会停止后续执行并返回错误这个认知要提前建立。6. 生产环境里踩过的坑事务使用细节与集群注意事项6.1 坑把 DISCARD 当成回滚工具我接手过一次线上问题有个定时任务处理账单处理到一半发现数据有问题代码里来了一句 DISCARD。本意是“事务出了问题就回滚”结果 DISCARD 只是清空了还没执行的队列前面已经 EXEC 成功的数据纹丝不动最后只能靠补偿任务手动修数据。记住一个结论Redis 里没有反向操作DISCARD 针对的是“未执行的事务”不是“已执行的结果”。如果业务真的需要回滚唯一可行的方案是在事务执行前保存旧值出错后主动写回旧值而且这个过程本身也要考虑并发问题。6.2 坑WATCH 之后做长耗时操作WATCH 本身没有超时但它要求从 WATCH 到 EXEC 之间键状态不能被修改。如果你在这期间执行了耗时的网络请求、复杂计算那么监视时间无限拉长高并发下几乎每秒都会冲突重试风暴一下子就把资源打满。我的建议是WATCH 之后只做本地的、快速的操作把外部 IO 和复杂逻辑全部放到 WATCH 之前如果发现重试超过两三次就应该考虑用 Lua 脚本替代让检查和更新在同一条链路里完成。6.3 坑集群模式下事务的键要落到同一个 slotRedis 集群要求一条命令的多个 key 必须在同一个哈希槽否则会返回 CROSSSLOT 错误。事务也一样MULTI 里如果出现了不同 slot 的 key客户端很难优雅处理。所以在集群架构里事务设计时必须先确认所有 key 的哈希标签一致比如给相关键都加上{order:1001}这种 tag。不少人在单机环境开发时习惯散着命名键迁移到集群才被事务问题绊倒我一般会在项目初期就约定好 key 命名时的哈希标签规则。6.4 一点个人习惯线上我用 Redis 事务的场景其实不算多反而要求自己更严格事务命令越短越好最好不超过 10 条入队前能预检的类型错误绝不放到执行阶段涉及关键金额、库存的更新一律优先 Lua因为 Lua 能在一个原子单元里完成读检查和写入省去 WATCH 重试的复杂度。我印象很深的是有一次并发秒杀场景初始方案是 WATCH MULTI 重试压测到 2000 并发时重试率肉眼可见地升高改成 Lua 脚本后延迟和成功率都稳定了下来。这是我个人在实际项目里反复试出来的经验。事务在 Redis 里不是万能药但把它的边界摸清楚后你会发现它在很多场景下依然是性价比很高的方案。如果你刚开始接触建议不要只在文档里读打开 redis-cli 把上面这几个例子亲手敲一遍特别是那两种错误处理的结果——一个 EXECABORT一个带回滚错觉的报错——一定会有更深印象。后续如果大家感兴趣我可以再整理一篇 Lua 脚本在业务里的落地实践。

关于恒美微站

恒美微站专注于为个体商户、工作室提供极简自助建站服务,让每个人都能轻松拥有专业网站。

快速链接

  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心

服务项目

  • 可视化建站
  • 拖拽编辑
  • 主题定制
  • SEO 优化
  • 网站托管

联系方式

  • 📍 地址:北京市朝阳区建国路 88 号
  • 📞 电话:400-888-8888
  • ✉️ 邮箱:info@hmyw.cn
  • 🕐 时间:周一至周日 9:00-18:00

© 2024 恒美微站 hmyw.cn 版权所有 | 京 ICP 备 12345678 号