Redis 作为高性能内存数据库,在缓存、计数器、排行榜等场景中被广泛使用。但很多人在使用 Redis 事务时,常常会带着 MySQL 事务的思维方式,从而产生误解:为什么 Redis 事务不支持回滚?为什么事务中读命令会立即执行?WATCH 到底算不算"锁"?
这篇文章将围绕这些问题,系统讲清 Redis 事务的设计理念、底层原理、执行流程以及工程实践中的正确使用姿势,帮助你在面试和真实项目中都能"用对、说清"。
旧版本Redis官网上关于 事务介绍的一个截图
一、Redis 事务与 MySQL 事务的核心差异
在理解 Redis 事务之前,必须先放弃一个错误前提:Redis 事务并不是 MySQL 事务的轻量版。两者的设计目标完全不同。
1. 原子性的本质区别
MySQL 事务追求的是严格的 ACID 原子性:
-
事务中的多条 SQL 要么全部成功
-
只要有一条失败,就通过 Undo Log 回滚所有已执行操作
-
对外呈现为"事务从未发生过"
Redis 事务追求的则是"命令队列级原子性":
-
保证事务内的命令会被完整地、按顺序执行
-
执行过程中不会被其他客户端的命令插入
-
不支持回滚,即使某条命令执行失败,后续命令仍然会继续执行
这并不是 Redis 的"缺陷",而是一个有意为之的取舍 :在 Redis 看来,运行期的命令失败通常是程序 Bug(如类型错误),而不是运行时异常,不应该交给事务系统兜底。
2. 实现机制的不同
MySQL 为了实现事务,需要付出巨大的系统成本:
-
Undo Log 用于回滚
-
Redo Log 用于崩溃恢复
-
行锁 / 表锁 / MVCC 用于隔离
-
支持多种隔离级别
Redis 的实现则极度克制:
-
使用
MULTI / EXEC缓存命令队列 -
使用
WATCH实现乐观锁 -
所有事务按顺序串行执行
-
只支持"串行化"这一种隔离语义
这也解释了一个现象:Redis 事务几乎没有额外性能负担。
3. 设计目标的差异
|------|------------|-----------|
| 对比维度 | MySQL | Redis |
| 核心目标 | 数据一致性 | 性能与吞吐 |
| 数据模型 | 关系型 | KV / 数据结构 |
| 并发控制 | 悲观锁 + MVCC | 乐观锁 |
| 使用场景 | 核心数据 | 缓存 / 派生数据 |
结论一句话:
Redis 事务是为"快"服务的,而不是为"严"服务的。
二、Redis 事务的核心特性与执行流程
1. Redis 事务的四大特性
① 打包执行
MULTI 开启事务后,所有写命令不会立即执行,而是被放入事务队列中。
bash
MULTI
SET k1 v1
SET k2 v2
此时 Redis 只会返回 QUEUED,并不会真正修改数据。
② 顺序执行(串行化)
当客户端执行 EXEC 时:
-
Redis 会按命令入队顺序依次执行
-
中间不会插入其他客户端的命令
-
整个执行过程是"不可打断"的
这是 Redis 事务最核心的保证。
③ 无回滚机制
如果事务中的某条命令在执行阶段失败:
-
Redis 不会回滚已执行的命令
-
后续命令仍会继续执行
例如:
bash
SET num 10
MULTI
INCR num
LPUSH num 1 # 类型错误
INCR num
EXEC
最终结果是:
-
第一次 INCR 成功
-
LPUSH 失败
-
第二次 INCR 仍然执行
这正是 Redis 所谓的"命令原子性而非结果原子性"。

④ 乐观锁控制(WATCH)
Redis 通过 WATCH 提供了一种轻量级并发控制手段,用于解决并发修改问题。
2. Redis 事务的完整执行流程
-
客户端发送
MULTI,Redis 进入事务状态 -
写命令进入事务队列,返回
QUEUED -
(可选)使用
WATCH监控关键键 -
客户端发送
EXEC-
若 WATCH 键未变化:执行事务
-
若 WATCH 键已变化:事务终止,返回
nil
-
-
客户端也可使用
DISCARD主动取消事务

三、WATCH 命令的乐观锁原理与实践
1. WATCH 的底层机制
Redis 为每个键维护一个内部版本号:
-
键被修改时,版本号递增
-
WATCH会记录当前版本号快照 -
EXEC时对比版本号是否发生变化
这本质上就是典型的乐观 锁 实现。
2. 转账场景示例
bash
WATCH account:1 account:2
GET account:1
GET account:2
MULTI
DECRBY account:1 30
INCRBY account:2 30
EXEC
若执行期间任何一个账户被其他客户端修改,事务将直接失败,由客户端决定是否重试。
3. WATCH 使用中的常见问题
-
监控键过多:增加版本对比成本,建议 ≤ 10 个
-
高并发重试风暴:说明业务层存在热点竞争
-
版本号溢出:理论存在,实践中几乎不可能
四、工程实践中的正确使用姿势
1. 不要在事务中依赖读命令
读命令会立即执行,不受事务保护。
正确做法:
-
在事务外先读
-
在事务中只做"基于已知值的写"
2. 哪些场景适合用 Redis 事务
-
批量写缓存
-
简单库存扣减
-
防止竞态条件
不适合:
-
复杂业务规则
-
强一致性金融场景
3. 事务 vs Lua 脚本
Lua 脚本:
-
天生原子执行
-
支持条件判断和流程控制
-
性能优于 WATCH + MULTI
复杂逻辑 优先 Lua,事务更多用于简单并发控制。

五、总结
Redis 事务不是关系型数据库事务的替代品,而是一种服务于高性能场景的轻量级原子操作机制。
-
MULTI/EXEC 保证命令的顺序与完整执行
-
WATCH 提供乐观锁能力
-
无回滚是性能与复杂度权衡的结果
真正的 Redis 工程能力,不在于"会不会用事务",而在于:
知道什么时候该用事务,什么时候该用 Lua,什么时候该交给数据库。
理解这一点,你就已经超过了绝大多数"只会背命令"的使用者。
