介绍
Redis 事务可以一次执行多个命令, 并且带有以下三个重要的保证:
- 批量操作在发送 EXEC 命令前被放入队列缓存。
- 收到 EXEC 命令后进入事务执行,事务中任意命令执行失败,其余的命令依然被执行。
- 在事务执行过程,其他客户端提交的命令请求不会插入到事务执行命令序列中。
一个事务从开始到执行会经历以下三个阶段:
- 开始事务。
- 命令入队。
- 执行事务。
基本命令
- multi:标记事物块的开始
- exec:执行所有事物块的命令,执行完后会取消事物
- discard:取消事物,放弃执行事物块的所有命令
- watch key [key …]:监视一个(或多个) key ,如果在事务执行之前这个(或这些) key 被其他命令所改动,那么事务将被打断。
- unwatch:取消监视
示例
1、执行事物
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set a aaa
QUEUED
127.0.0.1:6379> set b bbb
QUEUED
127.0.0.1:6379> set c ccc
QUEUED
127.0.0.1:6379> exec
1) OK
2) OK
3) Ok
如果在 set b bbb 处失败,set a 已成功不会回滚,set c 还会继续执行。
事物中的异常
编译时异常
在事物块中如果有命令有语法错误,在最终执行exec命令的时候,所有命令都将不会执行。
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set aa aa
QUEUED
127.0.0.1:6379> set ss
(error) ERR wrong number of arguments for 'set' command
127.0.0.1:6379> exec #执行时发现语法错误,将不会执行任何命令
(error) EXECABORT Transaction discarded because of previous errors.
运行时异常
在事物块中如果有命令在执行的时候,出现了错误,不会影响其他命令正常执行。
127.0.0.1:6379> set k1 v1
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> incr k1
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> exec
1) (error) ERR value is not an integer or out of range #由于k1的值是字符串,所以并不能自增
2) OK #这时候第二条命令还是可以正常执行的
SpringBoot实现Redis事物
1、开启事物支持
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisSerializer<Object> serializer = redisSerializer();
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory);
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(serializer);
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.setHashValueSerializer(serializer);
redisTemplate.afterPropertiesSet();
//开启事物支持
template.setEnableTransactionSupport(true);
return redisTemplate;
}
2、操作事物
错误操作
redisTemplate.multi();
redisTemplate.opsForValue().set("a","a");
redisTemplate.exec();
使用上面的方法会报,有知道的大佬可以在评论区留言
io.lettuce.core.RedisCommandExecutionException: ERR EXEC without MULTI
正确操作
SessionCallback<Object> callback = new SessionCallback<Object>() {
@Override
public Object execute(RedisOperations operations) throws DataAccessException {
operations.multi();
operations.opsForValue().set("aa","aa");
return operations.exec();
}
};
redisTemplate.execute(callback);
开启事物也可以在operations.multi();前面写上operations.setEnableTransactionSupport(true);
为什么redis不支持事物回滚
命令只会因为语法的问题导致失败,或是命令使用在了错误类型的key上面,也就是说,从实用性角度来说,失败的命令都是因为编程错误导致的,而这些错误应该在开发环境就应该发现,而不是应该出现在生产环境, 因为不需要对回滚进行支持,所以 Redis 的内部可以保持简单且快速。
乐观锁介绍
乐观锁是无论什么时候,始终都不会认为这条数据会被其他线程所修改,而是在修改时判断version是否跟取出来的version候一样,如果一样就进行修改,不一样则修改失败,修改失败只能继续查询这条数据然后继续修改,或者直接中断此次操作,告诉用户修改失败。
实现乐观锁
1、监控事物
127.0.0.1:6379> watch money
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> incrby money 50
QUEUED
2、再开一个客户端
127.0.0.1:6379> incrby money 50
OK
3、然后到之前的客户端执行exec
127.0.0.1:6379> exec
(nil)
这时候发现提示nil,说明被监控的key如果在执行过程中,如果值发生了变化,事物将会被打断,并且会取消监视。
4、重新执行
127.0.0.1:6379> watch money
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> incrby money 50
QUEUED
127.0.0.1:6379> exec
1) (integer) 200
总结
1.事物块中的所有命令按照先进先出的方式执行。
2.redis事物当一个命令失败后,不会回滚已执行成功的命令,也不会影响后续的操作
3.使用watch必须要写在multi命令前面。
4.事务在执行过程中不会被中断,当事务队列中的所有命令都被执行完毕之后,事务才会结束。
5.带有watch命令的key如果在事物执行前,如果值发生了变化,则会中断事物操作。