Redis中的事务

Redis中的事务(transaction)是一组命令的集合。事务同命令一样都是Redis的最小执行单位,一个事务中的命令要么都执行,要么都不执行。事务的应用非常普遍,如银行转账过程中,A给B汇款,首先系统从A的账户中将钱划走,然后向B的账户增加相应的金额。这两个步骤必须属于同一个事务,要么全执行,要么全不执行。否则只执行第一步,钱就凭空消失了,这显然让人无法接受。事务的原理是先将属于一个事务的命令发送给Redis,然后再让Redis依次执行这些命令。
例如:

redis> MULTI
OK
redis> SADD "user:1:following" 2
QUEUED
redis> SADD "user:2:followers" 1
QUEUED
redis> EXEC
1) (integer) 1
2) (integer) 1

上面的代码演示了事务的使用方式。

  • 1.首先使用MULTI命令告诉Redis:“下面我发给你的命令属于同一个事务,你先不要执行,而是把它们暂时存起来。”Redis回答:“OK。”

  • 2.而后我们发送了两个SADD命令来实现关注和被关注操作,可以看到Redis遵守了承诺,没有执行这些命令,而是返回QUEUED表示这两条命令已经进入等待执行的事务队列中了。

  • 3.当把所有要在同一个事务中执行的命令都发给Redis后,我们使用EXEC命令告诉Redis将等待执行的事务队列中的所有命令(即刚才所有返回QUEUED的命令)按照发送顺序依次执行。 EXEC命令的返回值就是这些命令的返回值组成的列表,返回值顺序和命令的顺序相同。

  • 4.Redis保证一个事务中的所有命令要么都执行,要么都不执行。如果在发送EXEC命令前客户端断线了,则Redis会清空事务队列,事务中的所有命令都不会执行。而一旦客户端发送了EXEC命令,所有的命令就都会被执行,即使此后客户端断线也没关系,因为Redis中已经记录了所有要执行的命令。

  • 5.除此之外, Redis的事务还能保证一个事务内的命令依次执行而不被其他命令插入。试想客户端A需要执行几条命令,同时客户端B发送了一条命令,如果不使用事务,则客户端B的命令可能会插入到客户端A的几条命令中执行。如果不希望发生这种情况,也可以使用事务。

错误处理

有些读者会有疑问,如果一个事务中的某个命令执行出错,Redis会怎样处理呢?要回答这个问题,首先需要知道什么原因会导致命令执行出错。

语法错误。

语法错误指命令不存在或者命令参数的个数不对。比如:

redis> MULTI
OK
redis> SET key value
QUEUED
redis> SET key
(error)ERR wrong number of arguments for 'set' command
redis> ERRORCOMMAND key
(error) ERR unknown command 'ERRORCOMMAND'
redis> EXEC
(error) EXECABORT Transaction discarded because of previous errors.

跟在MULTI命令后执行了3个命令:一个是正确的命令,成功地加入事务队列;其余两个命
令都有语法错误。而只要有一个命令有语法错误,执行EXEC命令后Redis就会直接返回错误,
连语法正确的命令也不会执行①。

注释:①Redis 2.6.5之前的版本会忽略有语法错误的命令,然后执行事务中其他语法正确的命令。就此例而言, SET key value会被执行, EXEC命令会返回一个结果: 1) OK。

运行错误。

运行错误指在命令执行时出现的错误,比如使用散列类型的命令操作集合类型的键,这种错误在实际执行之前Redis是无法发现的,所以在事务里这样的命令是会被Redis接受并执行的。如果事务里的一条命令出现了运行错误,事务里其他的命令依然会继续执行(包括出错命令之后的命令),示例如下:

redis> MULTI
OK
redis> SET key 1
QUEUED
redis> SADD key 2
QUEUED
redis> SET key 3
QUEUED
redis> EXEC
1) OK
2) (error) ERR Operation against a key holding the wrong kind of value
3) OK
redis> GET key
"3"

总结

  • 1.可见虽然SADD key 2出现了错误,但是SET key 3依然执行了。
  • 2.Redis的事务没有关系数据库事务提供的回滚(rollback)①功能。
  • 3.为此开发者必须在事务执行出错后自己收拾剩下的摊子(将数据库复原回事务执行前的状态等)。
  • 4.不过由于Redis不支持回滚功能,也使得Redis在事务上可以保持简洁和快速。
  • 5.另外回顾刚才提到的会导致事务执行失败的两种错误,其中语法错误完全可以在开发时找出并解决,另外如果能够很好地规划数据库(保证键名规范等)的使用,是不会出现如命令与数据类型不匹配这样的运行错误的。