MULTI、EXEC、DISCARD和WATCH是Redis事务相关的命令。
Redis的一个事务从开始到执行会经历以下三个阶段:
- MULTI命令开启事务
- 命令入队
- EXEC命令按顺序执行队列里的所有命令
事务可以一次执行多个命令,并且带有以下两个重要的保证:
- 事务是一个单独的隔离操作,事务中的所有命令都会序列化、按顺序地执行。
- 事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。
事务不会被其他client连接打断,当一个client在一个连接中发出multi命令后,这个连接会进入一个事务上下文,该连接后续的命令并不是立即执行,而是先放到一 个队列中。当从此连接收到exec命令后,redis会顺序的执行队列中的所有命令。并将所有命令的运行结果打包一起返回给client,然后此连接就结束事务上下文。
EXEC命令负责触发并执行事务中的所有命令:
- 如果客户端在使用MULTI开启了一个事务之后,却因为断线而没有成功执行EXEC,那么事务中的所有命令都不会被执行。
- 如果客户端成功在开启事务之后执行EXEC,那么事务中的所有命令都会被执行。
当使用AOF方式做持久化的时候,Redis会将事务中命令转化为单个命令后将其写入到磁盘中。
如果Redis服务器因为某些原因被管理员杀死,或者遇上某种硬件故障,那么可能只有部分事务命令会被成功写入到磁盘中。
如果Redis在重新启动时发现AOF文件出了这样的问题,那么它会退出,并汇报一个错误。
使用redis-check-aof程序可以修复这一问题,它会移除AOF文件中不完整事务的信息,确保服务器可以顺利启动。
Redis事务相关命令
1. MULTI
用于标记事务块的开始。Redis会将后续的命令逐个放入队列中,然后才能使用EXEC命令原子化地执行这个命令序列。
这个命令的返回值是一个简单的字符串,总是OK。
2. EXEC
在一个事务中执行所有先前放入队列的命令,然后恢复正常的连接状态。
当使用WATCH命令时,只有当受监控的键没有被修改时,EXEC命令才会执行事务中的命令,这种方式利用了检查再设置(CAS)的机制。
这个命令的返回值是一个数组,其中的每个元素分别是原子化事务中的每个命令的返回值。 当使用WATCH命令时,如果事务执行中止,那么EXEC命令就会返回一个空值nil。
3. DISCARD
清除所有先前在一个事务中放入队列的命令,然后恢复正常的连接状态。
如果使用了WATCH命令,那么DISCARD命令就会将当前连接监控的所有键取消监控。
这个命令的返回值是一个简单的字符串,总是OK。
4. WATCH
当某个事务需要按条件执行时,就要使用这个命令将给定的键设置为受监控的。
这个命令的返回值是一个简单的字符串,总是OK。
5. UNWATCH
清除所有先前为一个事务监控的键。
如果你调用了EXEC或DISCARD命令,那么就不需要手动调用UNWATCH命令。
这个命令的返回值是一个简单的字符串,总是OK。
使用方法
以下示例Redis事务:
redis> MULTI
"OK"
redis> INCR foo
QUEUED
redis> INCR bar
QUEUED
redis> EXEC
1) (integer) 1
2) (integer) 1
事务中的错误
在一个事务的运行期间,可能会遇到两种类型的命令错误:
- 事务在执行EXEC之前,入队的命令可能会出错。比如说,命令可能会产生语法错误(参数数量错误,参数名错误等等),或者其他更严重的错误,比如内存不足(如果服务器使用maxmemory设置了最大内存限制的话)。
- 命令可能在EXEC调用之后失败。举个例子,事务中的命令可能处理了错误类型的键,比如将列表命令用在了字符串键上面,诸如此类。
对于发生在EXEC执行之前的错误,客户端以前的做法是检查命令入队所得的返回值,如果命令入队时返回QUEUED,那么入队成功;否则,就是入队失败。
如果有命令在入队时失败,那么大部分客户端都会停止并取消这个事务。
不过,从Redis 2.6.5开始,服务器会对命令入队失败的情况进行记录,并在客户端调用EXEC命令时,拒绝执行并自动放弃这个事务。
在Redis 2.6.5以前,Redis只执行事务中那些入队成功的命令,而忽略那些入队失败的命令。 而新的处理方式则使得在流水线(pipeline)中包含事务变得简单,因为发送事务和读取事务的回复都只需要和服务器进行一次通讯。 至于那些在EXEC命令执行之后所产生的错误,并没有对它们进行特别处理,即使事务中有某个/某些命令在执行时产生了错误,事务中的其他命令仍然会继续执行。
为什么Redis不支持回滚(roll back)?
如果你有使用关系式数据库的经验,那么“Redis在事务失败时不进行回滚,而是继续执行余下的命令”这种做法可能会让你觉得有点奇怪。
以下是这种做法的优点:
- Redis命令只会因为错误的语法而失败(并且这些问题不能在入队时发现),或是命令用在了错误类型的键上面,这也就是说,从实用性的角度来说,失败的命令是由编程错误造成的,而这些错误应该在开发的过程中被发现,而不应该出现在生产环境中。
- 因为不需要对回滚进行支持,所以Redis的内部可以保持简单且快速。
有种观点认为Redis处理事务的做法会产生bug,然而需要注意的是,在通常情况下,回滚并不能解决编程错误带来的问题。举个例子,如果你本来想通过INCR命令将键的值加上1,却不小心加上了2,又或者对错误类型的键执行了INCR,回滚是没有办法处理这些情况的。
鉴于没有任何机制能避免程序员自己造成的错误,并且这类错误通常不会在生产环境中出现,所以Redis选择了更简单、更快速的无回滚方式来处理事务。
放弃事务
当执行DISCARD命令时,事务会被放弃,事务队列会被清空,并且客户端会从事务状态中退出。
如下示例:
redis> SET foo 1
OK
redis> MULTI
OK
redis> INCR foo
QUEUED
redis> DISCARD
OK
redis> GET foo
"1"
所有事务列表
可用版本 | 命令及描述 |
---|---|
>=2.0.0 |
取消一个事务队列中所有等待的指令,并且将连接状态恢复到正常。 |
>=1.2.0 |
执行事务中所有在排队等待的指令。 |
>=1.2.0 |
标记一个事务块的开始,随后的指令将在执行 EXEC 时作为一个原子执行。 |
>=2.2.0 |
刷新一个事务中已被监视的所有 key。 |
>=2.2.0 |
标记所有指定的 key 被监视起来,在事务中有条件的执行(乐观锁)。 |