redis事务
Redis 事务详解(基于 6.0.10 版本)
Redis 事务是一组命令的集合,通过序列化执行机制保证命令的原子性执行(非传统 ACID 原子性),适用于需要批量执行命令且不被中断的场景。本文将深入解析 Redis 事务的特性、命令流程及实际应用。
事务的核心特性
Redis 事务基于单线程架构设计,与传统数据库事务(如 MySQL)有显著区别:
- 串行化执行
事务中的所有命令会按顺序排队,在EXEC执行期间,Redis 不会插入其他客户端的命令,保证事务的独占性。 - 无隔离级别
事务提交前,所有命令仅入队不执行,因此:- 事务内的查询无法看到事务内其他命令的修改(未执行)。
- 事务外的查询也无法看到事务内的未执行命令,不存在 “脏读”“不可重复读” 等问题。
- 不保证原子性
- 若事务内存在语法错误(如命令拼写错误),
EXEC会拒绝执行所有命令。 - 若事务内存在运行时错误(如对字符串执行
INCR),Redis 会继续执行其他命令,仅返回该命令的错误,不支持回滚。
- 若事务内存在语法错误(如命令拼写错误),
事务命令详解
| 命令 | 作用 |
|---|---|
MULTI |
标记事务开始,后续命令进入队列(返回 QUEUED)。 |
EXEC |
执行队列中的所有命令,返回结果列表(若事务被中断,返回 nil)。 |
DISCARD |
取消事务,清空队列(需在 EXEC 前执行)。 |
WATCH key [key...] |
监视键,若事务执行前键被修改,事务会被中断(乐观锁机制)。 |
UNWATCH |
取消所有监视的键(EXEC 或 DISCARD 后会自动执行)。 |
事务执行流程
1. 正常执行
开始事务
127.0.0.1:6379> MULTI
OK
命令入队
127.0.0.1:6379> SET a 1
QUEUED
127.0.0.1:6379> INCR a
QUEUED
执行事务
127.0.0.1:6379> EXEC
1) OK
2) (integer) 2 # 最终 a 的值为 2
2. 语法错误导致事务失败
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> SET b 2
QUEUED
127.0.0.1:6379> INCR # 语法错误(缺少键)
(error) ERR wrong number of arguments for 'incr' command
127.0.0.1:6379> EXEC # 整个事务被拒绝
(error) EXECABORT Transaction discarded because of previous errors.
3. 运行时错误不影响其他命令
127.0.0.1:6379> SET c "abc" # 字符串值
OK
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> INCR c # 运行时错误(对字符串自增)
QUEUED
127.0.0.1:6379> SET d 4
QUEUED
127.0.0.1:6379> EXEC
1) (error) ERR value is not an integer or out of range # 错误命令
2) OK # 其他命令正常执行
127.0.0.1:6379> GET d # d 成功设置
"4"
WATCH 命令与乐观锁
WATCH 用于实现乐观锁,防止事务期间关键数据被修改:
基本用法
初始化余额
127.0.0.1:6379> SET balance 100
OK
监视 balance
127.0.0.1:6379> WATCH balance
OK
其他客户端修改 balance(模拟并发)
另一个终端:127.0.0.1:6379> SET balance 200
开始事务
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> DECRBY balance 50 # 入队
QUEUED
执行事务(因 balance 被修改,事务中断)
127.0.0.1:6379> EXEC
(nil)
127.0.0.1:6379> GET balance # 余额仍为 200
"200"
注意事项
WATCH必须在MULTI前执行,否则无效。- 事务执行后(无论成功与否),所有
WATCH会自动取消。 - 若需重试事务,需重新执行
WATCH和事务命令。
事务与管道(Pipeline)的结合
事务与管道结合可减少网络往返次数,提升批量操作性能:
Java 示例(使用 Spring Data Redis)
List<Object> results = stringRedisTemplate.executePipelined(
new SessionCallback<Object>() {
public Object execute(RedisOperations operations) throws DataAccessException {
operations.multi(); // 开启事务
operations.opsForValue().set("k1", "v1");
operations.opsForValue().increment("k2");
operations.exec(); // 执行事务
return null;
}
}
);
// 结果:[OK, 1]
- 管道:批量发送命令,减少网络 IO 次数。
- 事务:保证命令串行执行,不被其他请求中断。
实际应用建议
- 错误处理
执行EXEC后需遍历结果,对失败命令手动处理(如重试或补偿)。 - 替代方案
- 若需强原子性,可使用 Lua 脚本(Redis 保证脚本执行的原子性)。
- 分布式场景下,结合 Redisson 等框架实现分布式锁 + 事务。
- 性能优化
事务命令不宜过多(避免阻塞单线程),建议拆分大事务为多个小事务