Redis 命令详解:事务 Transaction
摘要
- 本文基于
redis-7.4.7 - Redis官网:https://redis.io/
Redis Transaction 简介
- 最关键的核心点是:Redis 的事务模型和关系型数据库(如 MySQL)的事务模型有本质区别。
- 关系型数据库事务:强调 ACID(原子性、一致性、隔离性、持久性),特别是在执行过程中,命令会看到一致的数据库状态(通过锁或MVCC),并且可以回滚。
- Redis 事务:主要目的是将多个命令打包,按顺序、连续、排他地执行。它更接近于一个批量执行和乐观锁的机制。它不提供回滚功能(只有命令入队时的语法检查,没有执行时的错误回滚)。
事务 Transaction 命令详解
-
Redis 事务命令总览表
| 命令 | 作用 | 所处阶段 | 返回值 | 典型使用场景 |
|---|---|---|---|---|
| MULTI | 开启一个事务,之后的命令进入队列 | 事务开始 | OK |
批量原子执行多个命令 |
| EXEC | 执行事务队列中的所有命令 | 事务提交 | 数组(每个命令的执行结果)或 nil |
提交事务 |
| DISCARD | 放弃事务队列中的所有命令 | 事务取消 | OK |
回滚未执行的事务 |
| WATCH key [key …] | 监控一个或多个 key 是否被修改(乐观锁) | 事务前 | OK |
并发控制、防止覆盖更新 |
| UNWATCH | 取消对所有 key 的监控 | 事务前 / 中 | OK |
主动释放监控 |
MULTI / EXEC —— 基本事务示例
-
可以在 MULTI 和 EXEC 之间使用的命令
1 | 几乎所有 “写操作” 和 “只读操作” 命令都可以放入 MULTI...EXEC 块中。例如: |
-
示例:原子递增两个计数器
1 | 127.0.0.1:6379> MULTI |
-
说明
- MULTI 后,所有命令只会进入队列,返回 QUEUED,不会立即执行。
- EXEC 时才真正执行。
- Redis 保证:
- 命令顺序执行
- 执行期间不会被其他客户端插入命令
- 但不支持回滚(某条命令失败,不会自动撤销已执行的命令)。
DISCARD —— 放弃事务
-
示例:取消事务
1 | 127.0.0.1:6379> MULTI |
-
说明
- DISCARD 会:
- 清空事务队列
- 退出事务状态
- 队列中的命令 不会被执行。
- DISCARD 会:
WATCH / EXEC —— 乐观锁示例
-
乐观锁事务模板
1 | WATCH key |
-
场景:实现一个安全的余额扣减逻辑,如果余额在事务执行前被别人修改,则放弃本次事务。
-
客户端 A
1 | 127.0.0.1:6379> SET balance 100 |
-
客户端 B(并发修改)
1 | 127.0.0.1:6379> INCRBY balance 50 |
-
客户端 A 提交事务
1 | 127.0.0.1:6379> EXEC |
-
说明
- 因为 balance 在 WATCH 之后被客户端 B 修改过:
- Redis 判定监控失败
- EXEC 返回 nil
- 事务不会执行
- 这是典型的 乐观锁(CAS)机制。
- 因为 balance 在 WATCH 之后被客户端 B 修改过:
UNWATCH —— 取消监控
-
示例:取消 WATCH 监控
1 | 127.0.0.1:6379> WATCH k1 k2 |
-
说明
- 取消所有被监控的 key。
- 常见用途:
- 逻辑判断失败,不再继续事务
- 主动释放监控,避免误触发事务失败
- 如果执行了 EXEC 或 DISCARD,Redis 会自动 UNWATCH。
关键行为总结(工程视角)
| 维度 | 行为 |
|---|---|
| 原子性 | EXEC 内命令顺序执行,不会被其他客户端插入 |
| 隔离性 | 不是数据库级事务隔离,仅保证执行期串行 |
| 回滚能力 | ❌ 不支持回滚 |
| 并发控制 | 通过 WATCH 实现乐观锁 |
| 失败行为 | WATCH 冲突 → EXEC 返回 nil |
| 性能 | 队列入内存,执行非常快 |
SpringBoot 中使用 Redis 事务
-
实际项目中Redis事务很少使用,因为
WATCH + MULTI的性能不如Lua或INCR等原子指令。 -
同一事务代码必须在同一线程内完成,推荐使用
SessionCallback
1 | String key = "stock:1001"; |
-
验证事务是否生效,可以在 redis 终端执行
MONITOR命令查询
1 | 127.0.0.1:6379> MONITOR |
重点看:
WATCH以及MULTI和EXEC之间的输出,要在一个连接中才能生效