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. 正常执行
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| # 开始事务 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. 语法错误导致事务失败
1 2 3 4 5 6 7 8 9
| 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. 运行时错误不影响其他命令
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| 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 用于实现乐观锁,防止事务期间关键数据被修改:
基本用法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| # 初始化余额 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)
1 2 3 4 5 6 7 8 9 10 11 12 13
| List<Object> results = stringRedisTemplate.executePipelined( new SessionCallback<Object>() { @Override public Object execute(RedisOperations operations) throws DataAccessException { operations.multi(); operations.opsForValue().set("k1", "v1"); operations.opsForValue().increment("k2"); operations.exec(); return null; } } );
|
- 管道:批量发送命令,减少网络 IO 次数。
- 事务:保证命令串行执行,不被其他请求中断。
实际应用建议
- 错误处理
执行 EXEC 后需遍历结果,对失败命令手动处理(如重试或补偿)。
- 替代方案
- 若需强原子性,可使用 Lua 脚本(Redis 保证脚本执行的原子性)。
- 分布式场景下,结合 Redisson 等框架实现分布式锁 + 事务。
- 性能优化
事务命令不宜过多(避免阻塞单线程),建议拆分大事务为多个小事务
v1.3.10