Redis基本事务操作详解
📌 一、事务基本概念与命令
Redis 事务通过以下命令实现多个操作的顺序化原子执行(命令队列整体执行,但无传统数据库的回滚机制):
MULTI
- 开启事务,后续命令进入队列而非立即执行,返回
QUEUED
表示入队成功。
- 开启事务,后续命令进入队列而非立即执行,返回
EXEC
- 提交事务,按顺序执行队列中所有命令,返回每个命令的结果数组。
DISCARD
- 取消事务,清空队列中的命令。
WATCH key...
- 监视键值,若被监视的键在事务提交前被修改,事务自动失败(乐观锁实现)。
UNWATCH
- 取消所有键的监视。
⚙️ 二、事务执行流程
- 开启事务
127.0.0.1:6379> MULTI # 开启事务 OK
- 命令入队
127.0.0.1:6379> SET user:A 500 # 命令加入队列 QUEUED 127.0.0.1:6379> INCR user:B 100 QUEUED
- 提交或取消
- 提交(
EXEC
):127.0.0.1:6379> EXEC 1) OK # SET 成功 2) (integer) 100 # INCR 成功
- 取消(
DISCARD
):127.0.0.1:6379> DISCARD # 清空队列 OK
- 提交(
⚠️ 三、事务错误处理
Redis 事务错误分为两类,处理方式不同:
- 入队错误(语法错误)
- 命令入队时检测到语法错误(如命令不存在),整个事务被拒绝执行。
127.0.0.1:6379> MULTI OK 127.0.0.1:6379> SET key1 "value" QUEUED 127.0.0.1:6379> NONEXISTING_CMD # 错误命令 (error) ERR unknown command 127.0.0.1:6379> EXEC (error) EXECABORT # 事务取消
- 执行错误(运行时错误)
- 命令执行时出错(如对字符串执行
INCR
),错误命令失败,其他命令继续执行(无回滚)。
127.0.0.1:6379> MULTI OK 127.0.0.1:6379> SET key1 "hello" QUEUED 127.0.0.1:6379> INCR key1 # 类型错误 QUEUED 127.0.0.1:6379> SET key2 "world" QUEUED 127.0.0.1:6379> EXEC 1) OK # 成功 2) (error) ERR value is not an integer # 失败 3) OK # 成功
- 命令执行时出错(如对字符串执行
🔒 四、WATCH 乐观锁机制
作用:解决并发冲突,确保事务执行期间被监视的键未被修改。
流程:
- 使用
WATCH
监视键(如余额)。 - 开启事务并提交命令。
- 若监视键被其他客户端修改,
EXEC
返回nil
表示事务失败。
Java 示例(Jedis):
jedis.watch("balance"); // 监视余额
int balance = Integer.parseInt(jedis.get("balance"));
if (balance >= 100) {
Transaction tx = jedis.multi();
tx.decrBy("balance", 100); // 扣款
tx.incrBy("debt", 100); // 增加债务
tx.exec(); // 提交事务(若 balance 被修改则失败)
} else {
System.out.println("余额不足");
}
失败处理:事务失败需客户端重试(如循环重试或补偿机制)。
🔄 五、Redis 事务 vs. 传统数据库事务
特性 | Redis 事务 | 传统数据库事务 |
---|---|---|
原子性 | 队列整体原子执行,但单命令失败无回滚 | 完全原子性(回滚支持) |
隔离性 | 无隔离级别,WATCH 实现乐观锁 | 支持多级别隔离(如读已提交) |
持久性 | 依赖持久化配置(RDB/AOF) | 事务提交即持久化 |
执行时机 | 命令在 EXEC 时一次性执行 | 命令实时执行 |
🧩 六、适用场景与限制
- 适用场景
- 批量操作:如转账(A 减余额,B 加余额)。
- 并发控制:结合
WATCH
实现库存扣减、抢购。
- 限制与规避
- 无回滚机制:需自行实现补偿逻辑(如日志记录 + 重试)。
- 阻塞命令禁用:事务中不可用
BLPOP
等阻塞命令。 - 集群限制:事务中所有键必须位于同一节点(相同 hash slot)。
- 替代方案
- Lua 脚本:原子执行复杂逻辑(如库存扣减 + 日志记录):
local stock = redis.call('GET', KEYS[1]) if tonumber(stock) >= tonumber(ARGV[1]) then redis.call('DECRBY', KEYS[1], ARGV[1]) return 1 -- 成功 else return 0 -- 库存不足 end
- Lua 脚本:原子执行复杂逻辑(如库存扣减 + 日志记录):
💎 总结
Redis 事务的核心价值在于 命令队列的顺序原子执行 与 WATCH
乐观锁的并发控制:
- 基础操作:掌握
MULTI
/EXEC
/DISCARD
的事务生命周期管理。 - 错误处理:区分入队错误(事务取消)与执行错误(部分成功)。
- 生产实践:
- 高并发场景优先使用
WATCH
+ 重试机制。 - 复杂事务用 Lua 脚本替代(原子性更强)。
- 避免大事务阻塞服务器,保持命令简洁。
- 高并发场景优先使用
通过合理设计重试逻辑与补偿机制,可在保证性能的同时实现业务一致性。