资讯动态

【Redis 进阶】事务深度解析:弱事务模型、WATCH 乐观锁与适用边界

发布时间:2026/9/16 11:27:15 来源:尧图企业网站定制
草莓熊Lotso个人主页❄️个人专栏:《C知识分享》 《Linux 入门到实践零基础也能懂》✨生活是默默的坚持毅力是永久的享受 博主简介文章目录前言一. 认识 Redis 事务1.1 原子性存在争议的弱化版本1.2 一致性不做保证1.3 隔离性天然满足1.4 持久性与事务无关1.5 事务的核心价值与适用场景二. 事务核心命令2.1 MULTI开启事务2.2 EXEC提交执行事务2.3 DISCARD放弃事务2.4 WATCH监控键值变更三. WATCH 的实现原理3.1 为什么需要 WATCH3.2 乐观锁与悲观锁3.3 版本号校验机制结尾前言接触过关系型数据库的开发者对 ACID 事务都不陌生。很多人初学 Redis 事务时会下意识拿 MySQL 的标准去对标结果越学越困惑它既没有回滚机制也没有隔离级别甚至业内对 “它到底算不算事务” 都有争议。事实上 Redis 事务走的是完全不同的轻量化路线它的核心目标不是实现强一致性而是把一组命令打包串行执行避免中间被其他客户端的命令插队。纠结 “有没有原子性” 没有太大意义理解它的设计取舍、执行流程和能力边界才能在实际业务中用对、用好。本文从 ACID 四大特性的对比切入完整拆解事务的执行流程、核心命令、WATCH 乐观锁的底层原理最后结合设计思路讲清它的适用场景与局限。一. 认识 Redis 事务我们以 MySQL 事务为参照从原子性、一致性、隔离性、持久性四个维度逐一拆解 Redis 事务的真实能力。1.1 原子性存在争议的弱化版本原子性最原始的定义是 “不可拆分”一组操作要么全部执行要么全部不执行。如果按这个狭义定义Redis 事务是满足的EXEC触发后队列里的命令要么全部跑一遍要么一个都不跑DISCARD则会清空队列所有操作都不执行。如果按 MySQL 的标准出错自动回滚Redis 事务不满足。事务执行过程中如果某条命令报错比如类型不匹配后面的命令会继续正常执行已经执行的操作不会回滚数据会停在中间状态。简单说Redis 事务只有批量执行的能力没有错误回滚的能力属于弱化版的原子性。这也是它最受争议的地方。1.2 一致性不做保证MySQL 的一致性指事务执行前后数据都符合业务约束不会出现非法的中间状态。 而 Redis 既没有数据库的约束机制又没有回滚能力事务执行中如果部分命令失败数据就可能出现不一致。一致性完全需要业务代码自己保证Redis 服务端不做兜底。1.3 隔离性天然满足MySQL 的隔离性要解决并发事务互相干扰的问题还分了四个隔离级别。 但 Redis 是单线程模型所有命令天然串行执行。事务一旦开始执行就会把队列里的所有命令连续跑完中间不会插入其他客户端的请求天然就是严格的串行隔离不需要额外的隔离级别实现。1.4 持久性与事务无关持久性指事务提交后修改就永久落盘。 但 Redis 是内存数据库持久化是独立的 RDB/AOF 机制和事务没有绑定关系。事务提交成功不代表数据已经写入硬盘能保存多久完全取决于持久化配置。 即使开启 AOF极端宕机也可能只写入了事务的部分命令。Redis 提供了redis-check-aof工具可以修复 AOF 文件移除不完整的事务让服务能正常启动。1.5 事务的核心价值与适用场景说了这么多 “弱”那事务的意义是什么 它最核心的作用就是打包命令防止插队。最经典的场景就是秒杀防超卖 如果不用事务先get库存再decr扣减两个操作之间可能插入其他客户端的命令导致库存判断失效最终出现超卖。 用事务把这两个操作打包就能保证它们连续执行中间不会被其他请求打断从根本上避免插队问题。补充Redis 原生命令不支持条件判断更复杂的业务逻辑可以用 Lua 脚本实现。Lua 脚本同样是原子执行能力比事务更强官方也更推荐用 Lua 替代事务。另外注意Redis 集群模式下不支持事务。二. 事务核心命令Redis 事务只有四个核心命令用法非常简单。2.1 MULTI开启事务执行MULTI后客户端进入事务状态。后续发送的写命令不会立即执行而是存入服务端为该客户端维护的事务队列中服务端返回QUEUED表示入队成功。127.0.0.1:6379MULTI OK127.0.0.1:6379setkey111QUEUED127.0.0.1:6379setkey2222QUEUED127.0.0.1:6379setkey3333QUEUED入队阶段的修改对其他客户端不可见只有真正执行事务后才会生效。2.2 EXEC提交执行事务执行EXEC后服务端会按顺序执行事务队列里的全部命令并把每条命令的结果依次返回。127.0.0.1:6379EXEC1)OK2)OK3)OK如果开启事务后服务端异常重启未提交的事务会全部丢失效果等同于放弃事务。2.3 DISCARD放弃事务执行DISCARD会清空当前事务队列取消本次事务所有入队的命令都不会执行。127.0.0.1:6379DISCARD OK执行后客户端退出事务状态回到正常模式。2.4 WATCH监控键值变更WATCH是事务的辅助命令用来监控一个或多个 key。如果在事务执行前被监控的 key 被其他客户端修改过整个事务就会放弃执行返回空结果。 它必须在MULTI之前使用相当于给EXEC加了一个前置校验条件。一个典型的并发修改场景# 客户端 A先监控 key再开启事务127.0.0.1:6379WATCH key OK127.0.0.1:6379MULTI OK127.0.0.1:6379SET key222QUEUED# 客户端 B在 A 执行 EXEC 前修改了 key127.0.0.1:6379SET key333OK# 客户端 A执行事务检测到 key 被修改事务放弃返回 nil127.0.0.1:6379EXEC(nil)# key 的值仍然是客户端 B 修改后的 333127.0.0.1:6379GET key333三. WATCH 的实现原理3.1 为什么需要 WATCH事务里的命令只是先入队不是立即执行。从MULTI到EXEC的时间窗口里key 完全可能被其他客户端改掉。如果不加校验事务还基于旧值做计算就会出现逻辑错误。 WATCH 就是为了解决这个问题提交前先检查数据有没有变变了就干脆不执行避免写出错误结果。3.2 乐观锁与悲观锁在讲实现之前先理清两个锁的概念悲观锁默认冲突概率很高操作前先加锁其他人必须等待。比如 C 的std::mutex特点是安全但开销大适合写多读少的场景。乐观锁默认冲突概率低操作前不加锁提交的时候再检查数据有没有被改过。没改就正常提交改了就放弃重试。特点是开销小适合读多写少的场景。WATCH 就是典型的乐观锁实现基于版本号机制工作。3.3 版本号校验机制WATCH 的底层逻辑和 CAS比较并交换思想高度一致每个 key 都维护一个隐式的版本号每次对 key 做修改操作版本号都会递增执行WATCH key时服务端记录下当前这个 key 的版本号客户端执行EXEC时服务端先校验所有被监控 key 的当前版本号和记录的版本号是否一致一致说明MULTI到EXEC期间 key 没被改过正常执行事务不一致说明期间被其他客户端修改过直接放弃整个事务返回 nil。和 CAS 类似它也存在经典的 ABA 问题数据被修改后又改回了原值版本号发生了变化但最终值看起来没变。不过绝大多数业务场景下这个问题不会造成实际影响。如果想取消监控执行UNWATCH即可会清除所有 key 的监控状态。源码视角事务的设计取舍站在系统设计的角度看Redis 事务之所以做得这么 “简陋”不是技术上做不到而是产品定位上刻意为之。队列式实现极致轻量每个客户端对应一个事务队列MULTI后命令入队EXEC批量顺序执行。没有 undo 日志、没有 redo 日志、没有复杂的状态机内存开销和实现成本都极低。 对于 Redis 来说核心定位是高性能缓存事务只是一个辅助能力不值得为它牺牲主流程的性能。单线程带来的天然隔离数据库为了实现隔离性要做 MVCC、要做锁、要做各种并发控制复杂度非常高。 而 Redis 单线程的模型天然就让所有命令串行执行事务执行过程中不可能被其他请求打断零成本就实现了严格的串行隔离。这也是架构选型带来的红利。为什么不做回滚这是最常被问到的问题。官方的设计逻辑很明确 事务中命令执行失败本质是编程错误比如对字符串用列表命令应该在开发阶段就避免而不是让服务端兜底。引入回滚就要维护中间状态、实现回滚逻辑代码复杂度和性能开销都会大幅上升和 Redis 追求极致性能的目标相悖。 用放弃回滚能力的代价换来更简单的实现和更高的运行效率这是非常典型的工程取舍。核心考点总结ACID 特性对比原子性仅保证 “要么全执行要么全不执行”不支持错误回滚属于弱化原子性一致性服务端不保证无约束无回滚需业务侧自行保障隔离性单线程串行执行天然满足无额外隔离级别持久性与事务机制无关取决于 RDB/AOF 持久化配置。核心命令与流程MULTI开启事务后续命令进入队列不立即执行EXEC提交事务按顺序执行队列中所有命令DISCARD放弃事务清空队列不执行WATCH前置监控 key被修改则事务整体放弃对应乐观锁UNWATCH取消监控。WATCH 底层原理基于版本号的乐观锁实现EXEC执行前校验版本不一致则事务整体失败必须在MULTI之前调用才能生效。适用边界适合简单批量操作防插队如秒杀防超卖不适合需要强一致、错误回滚的金融类场景复杂逻辑推荐使用 Lua 脚本替代事务Redis 集群模式下不支持事务。结尾 我是草莓熊 Lotso若这篇技术干货帮你打通了学习中的卡点 【关注】跟我一起深耕技术领域从基础到进阶见证每一次成长 ❤️ 【点赞】让优质内容被更多人看见让知识传递更有力量 ⭐ 【收藏】把核心知识点、实战技巧存好需要时直接查、随时用 【评论】分享你的经验或疑问比如曾踩过的技术坑一起交流避坑 ️ 【投票】用你的选择助力社区内容方向告诉大家哪个技术点最该重点拆解 技术之路难免有困惑但同行的人会让前进更有方向愿我们都能在自己专注的领域里一步步靠近心中的技术目标结语Redis 事务从来都不是 MySQL 事务的平替它是一个非常轻量化的批量执行工具解决的核心问题是「一组命令不被其他请求插队」。它弱在没有回滚、没有强一致也强在实现简单、运行高效足够应对绝大多数缓存场景的批量操作需求。技术选型从来不是功能越强大越好合适才最重要。理解它的能力边界在适合的场景使用它比纠结它 “算不算真正的事务” 有实际意义得多。✨把这些内容吃透超牛的放松下吧✨ʕ˘ᴥ˘ʔづきらど

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价