Redis事务的简单使用
问题:如果Java代码出现了异常,怎么对Redis进行回滚?
一次和朋友聊天聊到了这个问题,当时第一想法就是,try-catch异常,在catch里对之前插入到Redis的数据进行删除操作。但是接下来又有一个问题:如果在删除时报错了怎么办?
什么是事务?
学过关系型数据库的应该都知道,事务有一个ACID原则,即事务的四大特性:
atomicity(原子性):一个事务是一个不可分割的工作单位,事务中包括的操作要么都做,要么都不做consistency(一致性):事务必须是使数据库从一个一致性状态变到另一个一致性状态。一致性与原子性是密切相关的。isolation(隔离性):一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。durability(持久性):指一个事务一旦提交,它对数据库中数据的改变就应该是永久性的。接下来的其他操作或故障不应该对其有任何影响。
上面的ACID指的是关系型数据库,但是Redis作为一个NoSQL(非关系型数据库),它的事务肯定是和关系型数据库的不一样。
Redis的事务很简单,简单到它的本质其实就是一个队列。流程如下:

需要说明一下,加入到队列中的Redis命令并不会执行,只有输入了EXEC(提交事务)的命令后,才会依次执行队列中的命令。
事务的三个阶段:
- 开始事务
 - 命令入对
 - 执行事务
 
Redis事务的简单使用
- 使用
multi命令,开启事务 - 输入
redis命令 - 使用
exec命令,提交事务 
1  | 查看所有的Key  | 
可以看到,开启事务之前,什么数据都没有,只有执行EXEC(提交事务)命令后,才会依次执行被事务包含的命令,并打印OK。
Redis事务的“原子性”
atomicity(原子性)的要求就是,要么全部执行,要么全部不执行。但是Redis的原子性却有所不同,当事务提交提交之后,会依次执行队列中的命令,如果队列中的其中一个命令执行出错,并不会影响到其他的命令。显然这个不满足原子性的条件,所以Redis事务不是原子性的。
1  | 查看所有的Key  | 
可以看到,执行时报了(error) ERR value is not an integer or out of range错误,但是整体来看,只有incr age命令执行失败其它的都是执行成功 。所以可以看到,确实不符合原子性的要求。
注:Redis的单条命令是原子性的,但是事务不是原子性的。
Redis的事务“回滚”
严格来说,Redis的事务是不能回滚的。Redis只提供了一个DISCARD命令,作用就是取消事务。前面提到过,Redis的事务本质就是一个队列,开启事务之后的所有命令都是被放在队列中,只有执行EXEC(提交事务)命令,才会依次执行队列中的命令。执行DISCARD命令时,会放弃队列中的所用命令,结束本次事务,所以严格来说,Redis的事务是不能回滚的,只能取消,且取消操作需要在执行EXEC(提交事务)命令之前。
1  | 查看所有的Key  | 
可以看到,开启事务之前什么数据都没有,执行DISCARD(取消事务)命令后,也是什么数都没插入。
Redis事务的WATCH和Unwatch命令
WATCH:监视一个或者多个Key,如果在事务执行之前,被监控的Key的值如果被改变,则事务将会被打断。
Unwatch:放弃监控。
WATCH
监视Name
1  | 查看所有的Key  | 
创建一个新的绘画,修改name的值
1  | set name = list  | 
提交事务
1  | 提交事务  | 
可以看到,和之前的不同,执行exec后返回的不是OK而是nil。同时 事务中设置的name和age的值也不存在,只有在另一个会话中set的name。可以得出结论:被监控的Key的值如果被改变,则整个事务将会执行失败。
Unwatch
终端A
1  | 查看所有的Key  | 
终端B
1  | set name = list  | 
终端A
1  | set name wanger  | 
可以看到,执行了unwatch命令后,在终端B修改name的值,不会出现整个事务失败的问题。
总结
- Redis的单条命令支持原子性,但是事务不支持原子性。
 - Redis的事务是不能回滚的,但是可以取消事务(放弃事务)。
 - Redis事务有四个方法
- DISCARD:取消事务(放弃事务)
 - EXEC:提交事务(执行事务)
 - MULTI:开启事务
 - UNWATCH:取消所有监视
 - WATCH:监视1个或者多个Key
 
 





