上一篇我们深入学习了Redis RDB快照持久化,通过定时全量备份解决了Redis断电丢数据的问题,筑牢了数据安全的第一道防线。而在实际业务中,我们常常需要批量执行多条Redis命令,并且要求这些命令要么全部成功、要么全部不执行,避免中间状态导致数据错乱,比如转账扣款、库存扣减、批量更新缓存等场景。
针对这类批量原子操作需求,Redis提供了轻量级事务机制,依靠MULTI/EXEC/DISCARD/WATCH核心命令实现。不同于数据库的强事务,Redis事务更简洁、性能更高,本篇从零入门Redis事务,聚焦最常用的MULTI/EXEC组合,从命令实操、业务实战到核心局限性,带你吃透Redis事务的正确用法。
核心定位:Redis事务是一组命令的集合执行单元,保证批量命令串行执行、不被打断,实现简单的原子性;但不支持回滚,和关系型数据库事务有本质区别。
一、为什么需要Redis事务?
普通Redis命令是单条执行,高并发场景下多条命令之间会插入其他客户端的请求,导致数据不一致。比如批量扣减多个用户积分、同时更新缓存和计数器,中途被打断就会出现数据异常。
- 保证多条命令批量执行、不被插队,避免并发干扰
- 实现简单的业务原子性,杜绝中间状态数据
- 减少网络IO开销,批量命令一次性提交执行
- 配合WATCH命令,实现乐观锁,防止并发修改冲突
Redis事务的核心目标:批量命令串行化、一次性执行,杜绝命令插队,保证执行逻辑完整性。
二、Redis事务核心命令
Redis事务依靠4个核心命令完成,日常开发中MULTI+EXEC是最基础、最常用的组合,先掌握基础用法,再逐步拓展进阶命令。
| 命令 | 作用 | 执行时机 |
|---|---|---|
MULTI | 开启事务,标记命令队列开始 | 批量命令执行前 |
EXEC | 执行事务,一次性运行队列中所有命令 | 命令入队完毕后 |
DISCARD | 放弃事务,清空命令队列,取消事务状态 | EXEC执行前,取消事务 |
WATCH key [key...] | 监听Key,若Key被修改,事务执行失败 | MULTI开启事务前 |
Redis事务完整执行流程
- 开启事务:执行
MULTI命令,Redis进入事务模式 - 命令入队:后续执行的Redis命令不会立即运行,而是存入事务队列
- 执行/取消事务:执行
EXEC批量运行命令,或执行DISCARD清空队列 - 返回结果:EXEC执行后,按命令入队顺序返回所有结果
关键特性:命令入队时仅做语法校验,不执行;只有EXEC触发后才会批量执行,事务执行期间不会被其他客户端命令打断。
三、实操:MULTI/EXEC基础用法
通过Redis客户端,演示最基础的事务执行流程,实现批量设置用户信息、更新积分的原子操作。
1. 正常执行事务(成功案例)
# 1. 开启事务
127.0.0.1:6379> MULTI
OK
# 2. 命令入队(批量操作)
127.0.0.1:6379(MULTI)> SET user:1001:name "张三"
QUEUED # 命令入队成功,返回QUEUED
127.0.0.1:6379(MULTI)> INCRBY user:1001:score 10
QUEUED
127.0.0.1:6379(MULTI)> SET user:1001:age 25
QUEUED
# 3. 执行事务,批量运行所有命令
127.0.0.1:6379(MULTI)> EXEC
1) OK # 第一条命令结果
2) (integer) 10 # 第二条命令结果
3) OK # 第三条命令结果
2. 放弃事务(清空队列)
# 开启事务
127.0.0.1:6379> MULTI
OK
# 命令入队
127.0.0.1:6379(MULTI)> SET test:key 123
QUEUED
# 放弃事务,清空队列
127.0.0.1:6379(MULTI)> DISCARD
OK
# 再次执行命令,事务已取消
127.0.0.1:6379> GET test:key
(nil)
3. 命令语法错误(事务入队失败)
如果命令存在语法错误,入队时会直接报错,EXEC执行后整个事务全部不执行。
127.0.0.1:6379> MULTI
OK
# 错误命令,语法不合法
127.0.0.1:6379(MULTI)> SETTT user:1001 1
(error) ERR unknown command `SETTT`, with args beginning with: `user:1001`, `1`,
# 执行事务,因入队错误,事务失败
127.0.0.1:6379(MULTI)> EXEC
(error) EXECABORT Transaction discarded because of previous errors.
四、实战:SpringBoot整合Redis事务
基于SpringBoot+RedisTemplate,实现代码层面的Redis事务,适配批量更新缓存、计数器同步等业务场景。
第一步:开启RedisTemplate事务支持
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
// 开启事务支持
template.setEnableTransactionSupport(true);
// 序列化配置
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
return template;
}
}
第二步:代码实现MULTI/EXEC事务
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.List;
@Service
public class RedisTransactionService {
@Resource
private RedisTemplate<String, Object> redisTemplate;
/**
* 批量更新用户信息(Redis事务实现)
* @param userId 用户ID
* @param name 用户名
* @param score 积分
* @return 事务执行结果
*/
public List<Object> batchUpdateUserInfo(Long userId, String name, Integer score) {
try {
// 1. 开启事务
redisTemplate.multi();
// 2. 命令入队
String keyPrefix = "user:" + userId + ":";
redisTemplate.opsForValue().set(keyPrefix + "name", name);
redisTemplate.opsForValue().increment(keyPrefix + "score", score);
redisTemplate.opsForValue().set(keyPrefix + "status", 1);
// 3. 执行事务,返回批量结果
return redisTemplate.exec();
} catch (Exception e) {
// 异常时放弃事务
redisTemplate.discard();
throw new RuntimeException("Redis事务执行失败", e);
}
}
}
五、Redis事务的核心局限性(重点避坑)
很多新手会把Redis事务和MySQL事务等同,这是极大的误区,Redis事务不支持强一致性、不支持回滚,存在明显局限性。
1. 不支持命令执行失败回滚
如果命令入队时语法正确,执行时出现业务错误(如对String执行INCR),Redis不会回滚已执行的命令,错误命令会返回失败,其他命令继续执行。
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379(MULTI)> SET num abc # 设置字符串
QUEUED
127.0.0.1:6379(MULTI)> INCR num # 对字符串自增,执行时失败
QUEUED
127.0.0.1:6379(MULTI)> SET flag 1
QUEUED
# 执行事务
127.0.0.1:6379(MULTI)> EXEC
1) OK
2) (error) ERR value is not an integer or out of range # 单条命令失败
3) OK # 其他命令正常执行,无回滚
2. 不支持隔离级别
Redis事务仅保证自身命令不被打断,无法实现读未提交、读已提交等隔离级别,事务执行期间其他客户端仍可修改数据。
3. 无原子性保障(严格意义)
Redis事务仅保证命令批量执行,不满足ACID中的原子性,部分命令失败不会整体回滚,仅语法错误会导致整个事务取消。
4. 性能开销
事务命令需入队、批量执行,大量长事务会占用Redis内存,影响性能,不建议开启超大事务。
六、Redis事务适用场景与替代方案
✅ 适合场景
- 批量执行Redis命令,避免命令插队
- 缓存批量更新、计数器同步、简单数据写入
- 对性能要求高、允许少量数据不一致的场景
- 配合WATCH实现简单乐观锁,防止并发冲突
❌ 不适合场景
- 强事务需求:金融转账、订单支付、库存扣减(严格一致性)
- 需要回滚机制的核心业务
- 长事务、大批量命令执行
强一致性替代方案
- 分布式锁(SETNX):控制并发访问,保证单线程执行
- 消息队列:异步批量处理,保证最终一致性
- 数据库事务:核心业务优先使用MySQL等强事务
七、Redis事务最佳实践
- 保持事务精简:尽量缩短事务逻辑,减少命令数量,降低内存占用
- 禁用长事务:避免在事务中嵌套耗时业务,防止阻塞Redis
- 提前校验数据:入队前做好参数校验,避免执行时出现业务错误
- 高并发场景配合WATCH:监听关键Key,防止并发修改导致数据错乱
- 核心业务不用Redis事务:改用分布式锁+数据库事务,保证数据安全
Redis事务速记:MULTI开事务,命令入队列;EXEC批量跑,出错不回滚;轻量性能高,强事务别用。 核心命令:MULTI(开启)、EXEC(执行)、DISCARD(放弃)
结语与下篇预告
本篇我们掌握了Redis事务MULTI/EXEC的基础用法、实战落地和核心局限性,理清了Redis事务与数据库事务的本质区别,避免了业务踩坑。