Redis事务介绍
1. Redis 事务是什么
Redis 事务不是传统关系型数据库里那种带完整 ACID 语义的事务。
更准确地说,Redis 事务是一种:
- 把多条命令打包起来
- 按顺序执行
- 执行期间不被其他客户端命令插入
的机制。
Redis 事务最核心的命令有 3 个:
MULTIEXECDISCARD
另外还有一个非常关键的配套命令:
WATCH
2. Redis 事务的基本流程
2.1 MULTI:开启事务
客户端发送 MULTI 后,Redis 会进入事务上下文。
此时后续命令不会立刻执行,而是:
- 先进入队列
- 返回
QUEUED
例如:
MULTI
SET user:1:name alice
INCR counter:order
EXEC
在 EXEC 之前:
SETINCR
都只是入队,不会真正落地执行。
2.2 EXEC:提交事务
执行 EXEC 后,Redis 会把事务队列中的命令:
- 按顺序执行
- 一次性执行完
并返回每条命令的结果数组。
2.3 DISCARD:取消事务
如果在 EXEC 前决定不提交,可以执行 DISCARD:
- 清空事务队列
- 退出事务状态
3. Redis 事务到底保证了什么
3.1 保证“批量顺序执行”
Redis 事务最重要的保证是:
- 事务中的命令会按入队顺序执行
不会出现:
- 你的第 2 条命令先于第 1 条执行
3.2 保证“执行阶段不被其他命令打断”
Redis 单线程事件循环下,同一时刻核心命令执行路径是串行的。
所以 EXEC 开始后:
- 事务里的命令会连续执行
- 其他客户端命令不会插进中间
这一点是 Redis 事务的“原子性来源”。
但要注意:
- Redis 保证的是事务整体执行期间不被打断
- 不是说“事务中的所有业务逻辑都具备数据库级回滚能力”
3.3 不保证传统数据库意义上的回滚
这是 Redis 事务和 MySQL 事务最大的不同之一。
例如事务里有三条命令:
SET a 1INCR b- 某条命令执行时报错
Redis 不会像 MySQL 那样:
- 自动把前面已经执行成功的命令回滚掉
也就是说:
- Redis 事务没有传统意义上的 rollback
4. Redis 事务和关系型数据库事务有什么区别
4.1 Redis 不追求完整 ACID
关系型数据库事务强调:
- 原子性(Atomicity)
- 一致性(Consistency)
- 隔离性(Isolation)
- 持久性(Durability)
而 Redis 事务更像是:
- 命令批处理 + 顺序执行 + 简单乐观锁
它和 MySQL 事务的设计目标根本不同。
4.2 Redis 没有“执行失败自动回滚”
MySQL 中:
- 一个事务里后续语句失败
- 可以选择整体回滚
Redis 中:
EXEC一旦开始- 已成功执行的命令不会自动撤销
4.3 Redis 的隔离性也不是 MVCC / 锁事务那套
Redis 的“隔离”主要来自:
- 单线程串行执行
- 事务执行阶段不被打断
而不是:
- 行锁
- MVCC
- 两阶段锁
所以不要把 Redis 事务直接套用 MySQL 的事务模型去理解。
4.4 一张表看差异
| 维度 | Redis 事务 | MySQL 事务 |
|---|---|---|
| 核心机制 | 命令入队 + 顺序执行 | 日志 + 锁/MVCC + 提交回滚 |
| 执行期间是否被打断 | 不会 | 取决于锁与隔离级别 |
| 回滚 | 不支持传统自动回滚 | 支持 |
| 隔离实现 | 单线程串行执行 | 锁 / MVCC |
| 乐观并发控制 | WATCH |
版本号、条件更新等 |
| 适用场景 | 简单原子批处理 | 强一致事务业务 |
5. Redis 事务中的两类错误
这是面试里很容易问细的点。
5.1 入队阶段错误
如果在 MULTI 后,某条命令在入队阶段就有问题,例如:
- 命令名拼错
- 参数个数不对
那么这类错误会导致:
- 整个事务在
EXEC时被拒绝执行
也就是说,这种错误属于“事务还没真正开始执行就已经非法”。
5.2 执行阶段错误
另一类错误是命令能成功入队,但执行时失败。
例如:
SET counter hello
MULTI
INCR counter
SET user:1:name alice
EXEC
这里:
INCR counter会因为值不是整数而报错
但 Redis 的行为是:
- 这条命令报错
- 后面的命令仍然继续执行
- 不会自动回滚前面或后面的成功命令
所以 Redis 事务的错误处理一定要记住:
- 不是全有或全无
6. WATCH:Redis 的乐观锁
6.1 WATCH 是做什么的
WATCH 用来监视一个或多个 key。
如果在事务提交前,这些 key 被其他客户端修改了,那么:
- 当前事务的
EXEC会失败 - Redis 返回
nil/ 空结果 - 表示这次事务没有提交成功
它的本质是:
- 乐观锁(Optimistic Locking)
6.2 为什么需要 WATCH
因为单靠 MULTI / EXEC 只能保证:
- 一批命令顺序执行
但无法保证:
- 你“读到的数据”和“提交更新时的数据”之间没有被别人改过
这就是典型的“读-改-写竞争”问题。
例如你想实现:
- 先读库存
- 判断库存是否足够
- 再扣减库存
如果没有并发控制,就可能出现多个客户端同时读到旧值,然后都去扣减。
6.3 WATCH 的工作方式
典型流程如下:
WATCH stock:1001
GET stock:1001
MULTI
DECR stock:1001
EXEC
如果在:
WATCH stock:1001- 到
EXEC
之间,其他客户端修改了 stock:1001,那么当前 EXEC 会失败。
这意味着:
- Redis 没帮你自动重试
- 你需要在客户端重试整套逻辑
6.4 UNWATCH 是做什么的
UNWATCH 用来取消当前连接上对所有 key 的监视。
另外:
- 执行
EXEC - 执行
DISCARD
后通常也会自动取消监视状态。
7. Redis 事务的典型使用场景
7.1 多条命令需要连续执行
例如:
- 先写业务数据
- 再更新计数器
- 再记录日志索引
如果你希望:
- 这些命令不要被其他客户端插进来
那么可以使用事务。
7.2 基于 WATCH 的 CAS 更新
比如:
- 扣库存
- 扣余额
- 更新某个版本号
这类“先读后改”的逻辑,可以用 WATCH 做乐观控制。
7.3 简单原子批处理
有些场景不是强事务,只是想减少中间态暴露:
- 批量写多个 key
- 多步更新一个对象的多个字段
这时 MULTI / EXEC 可以让这批命令作为一个连续片段执行。
8. Redis 事务的局限性
8.1 不能替代数据库事务
如果你的业务要求:
- 强一致
- 回滚
- 隔离级别
- 跨多资源复杂事务
Redis 事务通常不够。
8.2 不能表达复杂业务逻辑
因为 Redis 事务里:
- 命令先入队
- 再一次性执行
所以你很难在事务内部写出复杂条件分支。
很多“读-判断-写”的逻辑如果拆成多次客户端交互,会暴露并发窗口。
8.3 WATCH 冲突时要自己重试
WATCH 失败后:
- Redis 不会自动帮你重试
- 需要客户端自己重新读、重新判断、重新提交
在高冲突场景下,重试成本可能比较高。
8.4 长事务队列没有关系型事务那种收益
Redis 事务不适合把大量命令长时间堆在一个事务里。
原因包括:
- 可读性差
- 一旦失败不好恢复
- 执行阶段会连续占用 Redis 处理时间
- 影响其他请求延迟
9. Lua 脚本和 Redis 事务是什么关系
9.1 为什么很多场景更推荐 Lua
在 Redis 里,很多人做原子操作时,第一反应不是事务,而是 Lua 脚本。
原因是 Lua 更适合表达:
- 读取
- 判断
- 写入
这一整段逻辑。
例如:
- 如果库存大于 0 就扣减,否则返回失败
用事务写会变成:
GET- 客户端判断
WATCHMULTIDECREXEC
而 Lua 可以把整个逻辑放在服务端一次完成。
9.2 Lua 的优势
Lua 脚本在 Redis 中执行时,同样具备:
- 原子执行
- 执行期间不被其他命令打断
但它比事务更强的地方在于:
- 可以在服务端写条件逻辑
- 可以减少客户端与 Redis 的往返次数
- 更适合“读-判断-写”型原子操作
9.3 什么时候用事务,什么时候用 Lua
可以简单这样区分:
- 事务
- 适合“多条命令打包顺序执行”
-
逻辑较简单
-
Lua
- 适合“读 + 判断 + 更新”一体化
- 需要服务端原子逻辑
10. Redis 事务和 Pipeline 的区别
这两个概念也容易混淆。
10.1 Pipeline 是什么
Pipeline 的目标是:
- 减少网络往返次数
它会把多条命令批量发送给 Redis,但:
- 不保证事务语义
- 不保证执行期间不被其他客户端命令插入
10.2 事务和 Pipeline 的核心区别
- 事务
-
强调顺序执行和执行阶段不被打断
-
Pipeline
- 强调减少 RTT、提升吞吐
一句话:
- Pipeline 解决的是网络开销
- 事务解决的是一组命令的原子连续执行