当前位置: 首页 > news >正文

【Redis从入门到精通】第55篇:Redis事务——MULTI/EXEC/DISCARD/WATCH详解

上一篇【第54篇】发布订阅实战——实时消息推送、聊天室、事件通知
下一篇【第56篇】Redis事务的ACID分析——它到底算不算ACID事务


如果你用过MySQL,一定很熟悉BEGIN ... COMMIT ROLLBACK。Redis也有事务,但它的"性格"跟MySQL完全不同——它更像是一个批量命令执行器,而不是传统意义上的数据库事务。

今天我们就来详细拆解Redis事务的四个命令(MULTI、EXEC、DISCARD、WATCH),看看它的实现原理和使用场景。


Redis事务的四个命令

Redis事务由以下四个命令组成:

命令功能类比SQL
MULTI标记事务开始,之后命令进入队列BEGIN TRANSACTION
EXEC执行事务队列中的所有命令COMMIT
DISCARD取消事务,清空队列ROLLBACK
WATCH监视一个或多个key,实现乐观锁SELECT … FOR UPDATE(但机制不同)

事务的工作流程

Redis 事务执行时序图 Client Redis Server │ │ │── MULTI ───────────────────────>│ 开启事务,返回OK │<── OK ──────────────────────────│ │ │ (命令入队,不执行) │── SET key1 value1 ─────────────>│ 入队 ✓ │<── QUEUED ──────────────────────│ │ │ │── SET key2 value2 ─────────────>│ 入队 ✓ │<── QUEUED ──────────────────────│ │ │ │── INCR counter ────────────────>│ 入队 ✓ │<── QUEUED ──────────────────────│ │ │ │── GET key1 ────────────────────>│ 入队 ✓ │<── QUEUED ──────────────────────│ │ │ │── EXEC ────────────────────────>│ 批量执行队列中的所有命令 │<── [OK, OK, 1, "value1"] ─────│ 返回所有命令的结果 │ │

关键点:MULTI之后、EXEC之前,所有命令都只是入队,不会实际执行。Redis也不会校验命令的合法性(比如类型错误)。只有当EXEC被调用时,才按顺序批量执行。


命令入队阶段

当客户端执行MULTI后,客户端的状态会切换为事务模式。在此模式下,每个命令都会被放入事务队列中。

事务队列的实现

在Redis的客户端结构中,事务队列存储在client.mstate中:

// client 结构中的事务状态typedefstructclient{multiState mstate;// 事务状态// ...}client;typedefstructmultiState{multiCmd*commands;// 命令数组(FIFO队列)intcount;// 命令数量// ...}multiState;typedefstructmultiCmd{robj**argv;// 命令参数数组intargc;// 参数个数structredisCommand*cmd;// 命令结构指针}multiCmd;
事务队列内存布局 client.mstate ┌──────────────────────────────────────────────────┐ │ count: 4 │ │ │ │ commands[0]: SET key1 value1 │ │ argv: ["SET", "key1", "value1"] │ │ cmd: setCommand │ │ │ │ commands[1]: SET key2 value2 │ │ argv: ["SET", "key2", "value2"] │ │ cmd: setCommand │ │ │ │ commands[2]: INCR counter │ │ argv: ["INCR", "counter"] │ │ cmd: incrCommand │ │ │ │ commands[3]: GET key1 │ │ argv: ["GET", "key1"] │ │ cmd: getCommand │ └──────────────────────────────────────────────────┘

入队阶段的两种错误

这是Redis事务中最容易踩坑的地方。入队阶段有两种截然不同的错误类型,处理方式也完全不同。

类型一:命令格式错误(语法错误)

如果入队的命令格式不对(比如拼写错误、参数个数不对),Redis会立即返回错误,同时将客户端标记为REDIS_DIRTY_EXEC

# 示例:命令拼写错误MULTI OK SET key value QUEUED INCR key1 key2 key3# INCR 只接受1个参数,这是语法错误(error)ERR wrong number of argumentsfor'incr'commandGET key QUEUED EXEC(error)EXECABORT Transaction discarded because of previous errors.

结果:整个事务被丢弃!一条命令语法错误,所有命令都不执行

类型二:运行时错误(类型错误)

如果命令格式正确,但运行时才发现类型不匹配,Redis会在EXEC时执行到那条命令时报错,但不影响其他命令的执行

# 示例:运行时类型错误SET key"hello"OK MULTI OK SET key"world"QUEUED INCR key# "world" 不是数字,运行时才报错QUEUED SET other_key"value"QUEUED EXEC1)OK ← SET成功2)(error)ERR value is not an integer or out of range ← INCR失败3)OK ← SET成功# 检查结果:GET key"world"← SET成功了,但INCR失败了,key还是"world"GET other_key"value"← 其他命令正常执行

结果:出错的命令被跳过,其他命令正常执行。Redis不会回滚

两种错误对比

入队阶段两种错误对比 ┌──────────────────────────────────────────────────────┐ │ 语法错误(入队时发现) │ │ │ │ MULTI → SET → INCR(参数错) → GET → EXEC │ │ OK QUEUED ERROR! QUEUED EXECABORT! │ │ │ │ 结果: 所有命令都不执行 │ └──────────────────────────────────────────────────────┘ ┌──────────────────────────────────────────────────────┐ │ 运行时错误(EXEC时发现) │ │ │ │ MULTI → SET → INCR(类型错) → SET → EXEC │ │ OK QUEUED QUEUED QUEUED 执行... │ │ OK │ │ ERROR │ │ OK │ │ │ │ 结果: 出错命令跳过,其他命令正常执行 │ └──────────────────────────────────────────────────────┘

踩坑提示:这是Redis事务和MySQL事务最大的区别之一。MySQL会回滚所有操作,Redis不会。所以在MULTI之前就要确保命令是正确的,特别是INCR、ZADD这类对值类型有要求的命令。


DISCARD 命令

DISCARD用于取消当前事务:

MULTI OK SET key1 value1 QUEUED SET key2 value2 QUEUED# 改变主意了,取消事务DISCARD OK# 此时事务队列已清空,客户端回到正常模式# key1 和 key2 都没有被设置

DISCARD做了三件事:

  1. 清空事务队列中的所有命令
  2. 清除客户端的REDIS_MULTI标志
  3. 如果有WATCH监视,也一并取消

WATCH 命令——乐观锁

WATCH是Redis事务中最有意思的命令。它实现了**乐观锁(Optimistic Locking)**机制,让你可以在事务执行前检查某个key是否被修改过。

WATCH 的工作流程

WATCH 乐观锁流程 Client-A Redis Client-B │ │ │ │── WATCH balance ──────────────>│ │ │<── OK ─────────────────────────│ │ │ │ │ │── GET balance ────────────────>│ │ │<── "1000" ─────────────────────│ │ │ │ │ │── MULTI ──────────────────────>│ │ │<── OK ─────────────────────────│ │ │── DECRBY balance 100 ─────────>│ (入队) │ │<── QUEUED ─────────────────────│ │ │ │ │ │ │← SET balance │ │ │ "500" ──────│ │ │← (balance被 │ │ │ Client-B修改)│ │ │ │ │── EXEC ────────────────────────>│ │ │<── nil ────────────────────────│ │ │ (事务被取消!balance │ │ │ 在WATCH后被修改了) │ │ │ │ │

WATCH 的实现原理

Redis在服务端维护了一个watched_keys字典:

// watched_keys 字典结构// Key: 被监视的Redis key// Value: 监视该key的客户端链表dict*watched_keys;// 示例:// "balance" → [Client-A, Client-C]// "stock" → [Client-B]
watched_keys 字典 ┌────────────┬──────────────────────────┐ │ Key │ Value (客户端链表) │ ├────────────┼──────────────────────────┤ │ "balance" │ [Client-A] → [Client-C] │ │ "stock" │ [Client-B] │ └────────────┴──────────────────────────┘ 当 "balance" 被修改时: ① 遍历 [Client-A, Client-C] ② 给每个客户端设置 REDIS_DIRTY_CAS 标志 ③ EXEC时检查该标志,如果被设置则返回nil

CAS 操作的代码示例

# 场景:实现一个安全的转账(A给B转100元)# --- 不使用WATCH(不安全)---GET balance:A"1000"# 如果此时别人也在操作A的余额...MULTI DECRBY balance:A100INCRBY balance:B100EXEC# 可能导致A的余额变成负数!# --- 使用WATCH(安全)---WATCH balance:A OK GET balance:A"1000"# 检查余额是否足够MULTI DECRBY balance:A100INCRBY balance:B100EXEC# 如果EXEC返回nil,说明balance:A在我们检查之后被修改了# 此时需要重试整个操作# --- 完整的CAS重试逻辑(伪代码)---def transfer(from_account, to_account, amount):whileTrue: WATCH from_account balance=GET from_accountifint(balance)<amount: UNWATCHreturn"余额不足"MULTI DECRBY from_account amount INCRBY to_account amount result=EXECifresult is not None:return"转账成功"# EXEC返回nil,说明被其他客户端修改了,重试

踩坑提示:WATCH + MULTI + EXEC 只能保证"如果key没变就执行,变了就不执行"。它不保证事务的完整性(如前所述,运行时错误不会回滚)。所以WATCH适合做CAS操作,但不适合需要严格一致性的场景。


事务在集群模式下的限制

在Redis Cluster中,事务有一个重要的限制:事务中的所有Key必须落在同一个槽位

# 集群模式下:# key "user:1001" 的槽位: 14520# key "user:1002" 的槽位: 12302MULTI SET user:1001"张三"QUEUED SET user:1002"李四"(error)CROSSSLOT Keysinrequest don'thashto the same slot# 解决方案:使用 {} 哈希标签MULTI SET{user}:1001"张三"# 槽位由"user"计算QUEUED SET{user}:1002"李四"# 槽位由"user"计算QUEUED EXEC1)OK2)OK# ✓ 成功!因为 {user} 确保了两个key在同一个槽位

MULTI/EXEC 嵌套的限制

Redis不支持嵌套事务。如果你在事务中又执行了MULTI,会得到错误:

MULTI OK SET key1 value1 QUEUED MULTI# 试图嵌套事务(error)ERR MULTI calls can not be nested EXEC1)OK

如果你确实需要在事务中做条件判断,请使用Lua脚本(下一篇会详细介绍),Lua脚本天然支持条件逻辑和原子性。


事务 vs 普通命令的性能

# 对比测试:1000次SET操作# 方式1: 逐条发送redis-benchmark-tset-n1000-c1# 约 10000 requests/sec# 1000次网络往返!# 方式2: 事务(MULTI ... EXEC)# 使用 pipeline + MULTI/EXECredis-benchmark-tset-n1000-P1-c1# 约 20000 requests/sec# 只需1次网络往返!# 方式3: Pipeline(不含事务)redis-benchmark-tset-n1000-P1000-c1# 约 100000 requests/sec# 1次网络往返,1000条命令!

事务的主要价值是减少网络往返(RTT),而不是原子性。如果你只需要减少RTT,Pipeline可能比事务更高效。


本章小结

Redis 事务核心要点 ┌─────────────────────────────────────────────┐ │ │ │ MULTI → 开启事务,命令入队 │ │ EXEC → 批量执行,返回所有结果 │ │ DISCARD → 取消事务,清空队列 │ │ WATCH → 乐观锁,监视key变化 │ │ │ │ 语法错误 → EXECABORT,全部不执行 │ │ 运行时错误 → 跳过错误命令,其余正常执行 │ │ 集群模式 → 所有key必须在同一槽位 │ │ 不支持嵌套 → MULTI不能嵌套 │ │ │ │ 本质: 批量命令执行器 + 乐观锁 │ │ 不是: 传统ACID事务 │ │ │ └─────────────────────────────────────────────┘
操作MULTI/EXECWATCH适用场景
批量执行-减少网络往返
条件执行CAS操作
错误回滚-需要回滚请用Lua
嵌套事务-用Lua脚本替代
条件判断-用Lua脚本替代

上一篇【第54篇】发布订阅实战——实时消息推送、聊天室、事件通知
下一篇【第56篇】Redis事务的ACID分析——它到底算不算ACID事务


http://www.jsqmd.com/news/949526/

相关文章:

  • VR-Reversal:免费解锁VR视频的终极观看指南,让3D内容在普通设备自由播放!
  • 2026年梅州市口碑首选!黄金回收铂金回收白银回收权威门店 TOP5 附咨询电话 - 信誉隆金银铂奢回收
  • 96110是什么电话?新流派带你了解反诈专线背后的秘密
  • 基于树莓派与OpenCV的实时人脸识别系统:从硬件搭建到算法部署全流程
  • Grok4 API低成本接入实战:绕过付费墙的合规工程路径
  • 软件开发模型——迭代模型
  • # 2026年烟台搬家公司实力排行榜,基于搬家行业的五大权威推荐榜单 - 十大品牌榜
  • 3PEAK思瑞浦 LMV358B-TSR TSSOP8 运算放大器
  • Qwen3.6-27B本地部署262K上下文:软硬件配置全解析
  • 2026国产数据库全景图:按架构、按行业、按能力三维度一表选型
  • 别只画图了!深度挖掘VOSviewer三大视图(网络/覆盖/密度)背后的科研故事与隐藏信息
  • 告别pip install失败:手把手教你搞定Python Click的离线安装(附国内镜像源大全)
  • VOCs检测车监控管理平台解决方案
  • 辽源市2026年黄金回收白银回收铂金回收权威门店 TOP5+正规可靠机构电话与地址汇总 - 中安检金银铂钻回收
  • 成本节省超30%!GPON OLT助力襄阳智慧物流园改造 - 资讯速览
  • 基于ESP32的独立CP/M模拟器:复古计算与现代硬件的完美融合
  • 终极Windows内核级硬件指纹伪装工具:EASY-HWID-SPOOFER完整指南
  • 上海租车合规选型全解析 资深从业者硬核经验分享 - 奔跑123
  • 盲审前最后一道防线,AIGC 检测误判与降痕全解析
  • 不用写代码!用Supervisely自带工具,4天搞定5711张人像分割数据集标注与格式转换
  • 2026年楚雄州黄金回收白银回收铂金回收门店 TOP5榜单无套路:实体店铺地址电话一览 - 诚金汇钻回收公司
  • 高并发服务器必备:小根堆定时器从设计到实现全流程
  • 5分钟终极指南:免费快速实现网盘直链下载的完整教程
  • 2026武汉特色湖北菜河鲜海鲜网红地标餐厅排行,晓江湖口碑 - 奔跑123
  • 生成式智能搜索下的流量卡位攻略:初创个体如何甄选高兼容性的 GEO 优化 服务商
  • 解密NomNom存档编辑器:三步搞定JSON导出异常问题
  • 2026年红河州黄金回收白银回收铂金回收门店 TOP5榜单无套路:实体店铺地址电话一览 - 诚金汇钻回收公司
  • 吕梁市2026年黄金回收白银回收铂金回收放心选真心推荐 靠谱门店排行 + 联系电话整理 - 中业金奢再生回收中心
  • Python量化交易实战:如何用jqktrader构建高效自动化交易系统
  • 出手西安闲置翡翠,为何优先选正规连锁实体店 - 奢侈品回收测评