说说你对Redis事务的理解?
1 个回答
看山,看水,看众生,也看自己。小王子-B491一、Redis 事务的基本概念 1. Redis 事务是用来**把一组命令打包成一个整体执行**的机制,对应的核心命令是: * `MULTI`:开启事务,进入事务队列模式 * 多条普通命令:此时不会立刻执行,只会返回 `QUEUED`,进入队列 * `EXEC`:提交事务,按顺序执行所有已入队命令 * `DISCARD`:放弃事务,清空命令队列 * `WATCH` / `UNWATCH`:实现乐观锁(CAS) 2. Redis 是**单线程**执行命令的,所以在 `EXEC` 阶段,这一批命令会**连续按顺序执行**,不会被其它客户端的命令插入。但这并不代表“从 MULTI 到 EXEC 整个过程都隔离”——这是很多人误解的点。 二、完整执行流程(含 WATCH) 1. 无 WATCH 的普通事务: ```text MULTI SET a 1 -> QUEUED INCR b -> QUEUED EXEC -> 依次执行 SET a 1、INCR b ``` * `EXEC` 之后,Redis 依次执行队列里的命令,这个过程是串行的、不可插入其它命令。 2. 带 WATCH 的乐观锁事务: 典型 CAS 模式(伪代码逻辑): ```text WATCH key1 key2 # 监视关键业务数据 val = GET key1 # 客户端自己计算新值 MULTI # 开启事务 SET key1 newVal INCR key2 EXEC # 提交事务 ``` * 如果在 `WATCH` 之后、`EXEC` 之前,有其他客户端修改了 `key1`/`key2`, 那么当前客户端执行 `EXEC` 时会直接返回 `nil`(事务被打断,里面的命令都不会执行)。 * 这就是 Redis 提供的**乐观锁机制**,由应用层自己决定是否重试。 3. 取消监视: * `UNWATCH`:取消当前连接上所有已监视的 key。 * `DISCARD`:不仅取消事务,还会自动取消所有 WATCH。 三、Redis 事务对 ACID 的支持情况 拿传统数据库的 ACID 来对比,是面试常问的角度: 1. Atomicity(原子性) * 粗略地说:`EXEC` 本身是**原子操作**,要么成功接收执行要么因为被 WATCH 打断而整体失败。 * 但细节很关键: * 如果队列中的某条命令在**执行时出错**(比如类型不匹配),前面已经执行的命令不会回滚, 事务只会在出错的位置报错,后面的命令继续尝试执行。 👉 所以 Redis 事务**不支持回滚**,不能保证“要么全部成功要么全部失败”的强原子性。 * 如果在排队阶段有语法错误,`EXEC` 会直接失败,整批命令都不会执行(Redis 新版本做了保护)。 * 准确说:Redis 只保证“命令队列在 EXEC 时以原子方式提交执行”,不保证“中途出错时回滚”。 2. Consistency(一致性) * Redis 本身不做复杂约束(没有外键、唯一约束之类的),一致性更多靠业务逻辑保障。 * 在事务内命令按顺序、连续执行,因此在单线程 + 正确业务逻辑下,一般能保持数据的一致性。 3. Isolation(隔离性) * 在 `EXEC` **执行的那一小段时间内**,事务里的命令不会被其他客户端命令插队,这可以视为一种“执行阶段的隔离”。 * 但在 `MULTI` 到 `EXEC` 之间,队列只是存在于当前连接,**数据本身完全可能被其他客户端修改**。 * 如果要避免被别人改坏,就要配合 `WATCH` 做 **乐观锁**: * 只要有被监视的 key 被改动,`EXEC` 直接失败,事务里的命令一个都不会执行。 * 所以 Redis **没有像数据库那样的隔离级别概念**(读已提交、可重复读之类),更接近“读已提交 + 乐观锁机制”。 4. Durability(持久性) * Redis 是否“持久”,其实取决于配置: * 用 RDB/AOF + 合理的 `appendfsync` 策略,可以做到类似持久化; * 但如果不开持久化,或者配置为 `appendfsync everysec`,在宕机时仍可能丢失最近一秒的数据。 * 所以持久性是**配置相关、非强保证**。 四、事务 vs Pipeline 的区别 很多人会混淆 Redis 事务和 Pipeline,面试里可以顺带讲一下: 1. 共同点: * 都会一次性把多条命令发给 Redis,降低网络往返开销。 2. 区别: * 事务(`MULTI`/`EXEC`)强调**原子性和顺序执行**; * Pipeline 只是**打包发送**,服务器仍然可以在命令之间穿插执行其它客户端的命令, 也不保证这些命令是一个“整体逻辑单元”,更谈不上 WATCH、执行失败整体回滚之类。 * 两者可以组合:在一个 Pipeline 里发送 MULTI、若干命令、EXEC,这样既减少 RTT 又实现事务语义。 五、Redis 事务的典型使用场景 1. 乐观锁更新(最典型) 用 `WATCH` 做 CAS,防止“读旧数据 + 写回覆盖”。比如扣库存、扣积分、更新余额时: ```text WATCH balance cur = GET balance if cur >= cost: MULTI DECRBY balance cost INCRBY expense cost EXEC # EXEC 返回 nil 则表示被别人改过,重试 else: UNWATCH # 余额不足 ``` 2. 简单的多 key 原子更新 * 比如同时更新几个计数器、多个相关 key 的状态,希望“要么一起生效,要么都不生效”, * 但要注意:如果中间某条命令有运行时错误,是不会回滚的,业务设计时要尽量避免。 3. 批处理 + 业务一致性要求不那么苛刻的场景 六、常见误区(面试里顺便帮忙纠正) 1. 误区: > “Redis 事务中的所有命令会被序列化并按顺序执行,过程中不受其他客户端命令的影响。” 更准确的说法是: * **只有在执行 `EXEC` 时,这批命令是一次性、按顺序、不中断地执行。** * 但从 `MULTI` 到 `EXEC` 之间,数据随时可能被其他客户端改动; * 是否因为外部修改而放弃事务,由 `WATCH` 来决定。 2. 误区: > “Redis 事务也支持回滚,和数据库一样。” 实际上: * Redis 不做执行时的回滚, * 要么因为 WATCH 冲突整批不执行,要么就执行到哪算哪,出错后不回退之前的操作。 总结: > Redis 事务本质上是基于 `MULTI/EXEC/WATCH` 的**乐观锁 + 顺序执行机制**,提供的是“按顺序一次性执行、一段时间内不被插队”的保证,但**不提供传统数据库那种强 ACID 事务(尤其是隔离性和回滚)**,更适合在业务端配合乐观锁和重试进行控制。编辑于 2024-09-13 11:45 •IP属地 北京举报评论收藏
公众号进入网站体验更佳

