Redis的事务基于四个命令:
- MULTI
- EXEC
- DISCARD
- WATCH
创建事务
Redis的事务从一个MULTI命令开始,MULTI总会命令返回"ok"。
接着就可以开始输入操作数据,每一条操作命令都会进入队列。
最后执行EXEC,在队列中的命令得到执行。
比如这样:
> MULTI
OK
> INCR foo
QUEUED
> INCR bar
QUEUED
> EXEC
1) (integer) 1
2) (integer) 1
如果事务中出现错误怎么办?
首先我们将事务中的错误分为两类:
- 进入队列前发现错误,比如命令的语法错误。
- EXEC执行后发现错误,比如对同一个key执行的两次不同数据类型的操作。
第一种很好理解,就像上面给出的例子中,成功进入队列后会立马返回"QUEUED"。
如果没能进入队列,则整个事务都会失效,提示"Transaction discarded because of previous errors."
关于EXEC执行后出现的错误,凡是成功进入队列的都会被执行,即便同一事物中有执行失败的命令,其余的命令都会得到执行。
这让那些用过关系型数据库的人们感到诧异, 为什么Redis不支持roll back?
对此官网的两点说法:
Redis commands can fail only if called with a wrong syntax (and the problem is not detectable during the command queueing), or against keys holding the wrong data type: this means that in practical terms a failing command is the result of a programming errors, and a kind of error that is very likely to be detected during development, and not in production.
Redis is internally simplified and faster because it does not need the ability to roll back.
有人说不支持roll back会引发各种bug。但需要明白的是,roll back不是用来解决编程层面上的错误的。
而且,导致命令失败只有两种可能:
- 语法错误
- 执行了与key的数据类型不匹配的操作
鉴于此,roll back几乎没有任何意义,Redis更倾向于不支持roll back而是保持简洁和高效。
丢弃事务
DISCARD可以用作丢弃当前事务,并将连接状态恢复为正常状态。
使用方法如下:
> SET foo 1
OK
> MULTI
OK
> INCR foo
QUEUED
> DISCARD
OK
> GET foo
"1"
原子性
check-and-set(CAS)之类的操作往往会出现竟态条件。
Redis提供了WATCH用于观察key的变化。
当一个被观察的key在EXEC执行前变化时,整个事务会终止并返回Nil,对该key的观察也会结束。
刚接触WATCH的时候,感觉这种处理方式很晦涩。不能加个显示锁什么的吗?
好在官网给出了充分的说明,先假设Redis没有提供INCR,我们现在要模拟一个递增操作:
val = GET mykey
val = val + 1
SET mykey $val
一个client进行该操作时是没有问题,但多个client一起进行该操作时会出现竟态条件。
比如A和B两个client同时进行了GET mykey,得到的都是10,因此两次执行的结果是11而不是12。
而加入WATCH后:
WATCH mykey
val = GET mykey
val = val + 1
MULTI
SET mykey $val
EXEC
如果WATCH和EXEC之间有其他的什么东东改变了被观察的key,该事务则会失败。
如果希望本次事务执行成功则需要在循环中执行,当然,这也是一种locking方式。
事实上,多个client访问同一key的冲突并不常见,看具体情况进行操作吧。
相应地,也有UNWATCH可以用于释放所有被观察的key。
比如下面的例子中,事务内的INCR会成功执行:
WATCH key
UNWATCH key
MULTI
INCR key
EXEC
关于WATCH,需要注意的一点是:设置了生存时间(EXPIRE)的key不会因为到期而被WATCH当作以改变。