redis(八)-事务
事务处理
总所周知,事务是指一个完整的动作,要么全部执行,要么全部失败。
事务的四大特性(简称ACID): 1、原子性(Atomicity):事务中的全部操作在数据库中是不可分割的,要么全部执行,要么均不执行。 2、一致性(Consistency):几个并行执行的事务,其执行结果必须与按某一顺序串行执行的结果相一致。 3、隔离性(Isolation):事务的执行不受其他事务的干扰,事务执行的中间结果对其他事务必须是透明的。 4、持久性(Durability):对于任意提交事务,系统必须保证该事务对数据库的改变不被丢失,即使数据库出现故障。
在聊redis事务处理之前,要先和大家介绍四个redis指令,即MULTI,EXEC,DISCARD,WATCH。这四个指令构成了redis事务处理的基础。
1、MULTI:组装一个事务
2、EXEC:执行一个事务
3、DISCARD:取消一个事务
4、WATCH:监视一些key,一旦这些key在事务执行前被改变,则取消事务的执行。
例子1:简单粗暴:
redis> MULTI //标记事务开始 OK redis> INCR user_id //多条命令按顺序入队 QUEUED redis> INCR user_id QUEUED redis> INCR user_id QUEUED redis> PING QUEUED redis> EXEC //执行 1) (integer) 1 2) (integer) 2 3) (integer) 3 4) PONG
解释:QUEUED表示我们在用MULTI组装事务时,每一个命令都会进入到内存队列中缓存起来,如果出现QUEUED则表示我们这个命令成功插入了缓存队列,在将来执行EXEC时,这些被QUEUED的命令都会被组装成一个事务来执行(如果中途语句出错,则执行命令EXEC会失败)。
例子2:有关事务,大家经常会遇到两种错误
1、调用EXEC之前错误
2、调用EXEC之后错误
”调用EXEC之前错误“有可能是语法有误导致,也可能是由于内存不足导致的。只要出现某个命令无法成功写入缓冲队列的情况,redis也会进行记录,在客户端调用EXEC时,redis会拒绝这一事务(这是redis2.6.5之版本之后的策略。在2.6.5之前的版本中,redis会忽略那些入队失败的命令,只执行那些入队成功的命令)。
127.0.0.1:6379> multi OK 127.0.0.1:6379> haha //一个明显错误的指令 (error) ERR unknown command 'haha' 127.0.0.1:6379> ping QUEUED 127.0.0.1:6379> exec //redis无情的拒绝了事务的执行,原因是“之前出现了错误” (error) EXECABORT Transaction discarded because of previous errors.
“调用EXEC之后错误”redis采取了完全不同的策略,即redis不会理睬这些错误,而是继续向下执行事务中的其他命令。这是因为,对于应用层面对错误,并不是redis自身需要考虑和处理的问题所以一个事务中如果一条命令执行失败了,并不会影响接下来的其他命令的执行。
127.0.0.1:6379> multi OK 127.0.0.1:6379> set age 23 QUEUED //age不是集合,所以如下是一条明显错误的指令 127.0.0.1:6379> sadd age 15 QUEUED 127.0.0.1:6379> set age 29 QUEUED 127.0.0.1:6379> exec //执行事务时,redis不会理睬第2条指令执行错误 1) OK 2) (error) WRONGTYPE Operation against a key holding the wrong kind of value 3) OK 127.0.0.1:6379> get age "29" //可以看出第3条指令被成功执行了
再来说下WATCH,这是一个很好的命令,它可以帮我们很好的实现类似于乐观锁的效果,即CAS(Check And Set)。WATCH本身的作用是监视key是否被改动过,而且支持同时监视多个key,只要还没出发事务,WATCH都会尽职尽责的监视,一旦发现谋个key被修改了,在执行EXEC时就会返回nil,表示事务无法触发。
127.0.0.1:6379> set age 23 OK 127.0.0.1:6379> watch age //开始监视age OK 127.0.0.1:6379> set age 24 //在EXEC之前,age的值被修改了 OK 127.0.0.1:6379> multi OK 127.0.0.1:6379> set age 25 QUEUED 127.0.0.1:6379> get age QUEUED 127.0.0.1:6379> exec //触发EXEC (nil) //事务无法被执行