Redis事务是原子操作，或者执行所有命令或者都不执行。 EXEC 命令触发一个事务中所有命令的执行，所以，如果一个客户端断在调用EXEC 命令前丢失连接，那么所有的命令不会被执行，相反，如果EXEC 被调用，那么所有命令会被执行。当使用 append-only file 方式持久化时，Redis使用单个 write(2) 系统调用将事务写到磁盘上。但是，如果Redis服务器崩溃或被系统管理员以某种硬方式杀死，则可能只注册了部分操作。Redis重启的时候会检测到这种情况，并返回错误退出。使用
从2.2起，Redis提供了额外的保证，以类似check-and-set (CAS)的乐观锁形式 。后面会详细介绍。
如果不调用EXEC，调用 DISCARD 会清空事务队列并退出事务。
> MULTI OK > INCR foo QUEUED > INCR bar QUEUED > EXEC 1) (integer) 1 2) (integer) 1
从上面例子可以看出， EXEC 返回一个应答数组，数组中的每个元素对应着事务中的一个命令，和命令发送的顺序一致。
Clients used to sense the first kind of errors, happening before the EXEC call, by checking the return value of the queued command: if the command replies with QUEUED it was queued correctly, otherwise Redis returns an error. If there is an error while queueing a command, most clients will abort the transaction discarding it.
However starting with Redis 2.6.5, the server will remember that there was an error during the accumulation of commands, and will refuse to execute the transaction returning also an error during EXEC, and discarding the transaction automatically.
Before Redis 2.6.5 the behavior was to execute the transaction with just the subset of commands queued successfully in case the client called EXEC regardless of previous errors. The new behavior makes it much more simple to mix transactions with pipelining, so that the whole transaction can be sent at once, reading all the replies later at once.
Errors happening after EXEC instead are not handled in a special way: all the other commands will be executed even if some command fails during the transaction.
This is more clear on the protocol level. In the following example one command will fail when executed even if the syntax is right:
Trying 127.0.0.1... Connected to localhost. Escape character is '^]'. MULTI +OK SET a 3 abc +QUEUED LPOP a +QUEUED EXEC *2 +OK -ERR Operation against a key holding the wrong kind of value
It’s important to note that even when a command fails, all the other commands in the queue are processed – Redis will not stop the processing of commands.
Another example, again using the wire protocol with
telnet, shows how syntax errors are reported ASAP instead:
MULTI +OK INCR a b c -ERR wrong number of arguments for 'incr' command
This time due to the syntax error the bad INCR command is not queued at all.
- Redis 命令只在两种情况失败：
Discarding the command queue
DISCARD can be used in order to abort a transaction. In this case, no commands are executed and the state of the connection is restored to normal.
> SET foo 1 OK > MULTI OK > INCR foo QUEUED > DISCARD OK > GET foo "1"
Optimistic locking using check-and-set
WATCH is used to provide a check-and-set (CAS) behavior to Redis transactions.
WATCHed keys are monitored in order to detect changes against them. If at least one watched key is modified before the EXEC command, the whole transaction aborts, and EXEC returns a Null reply to notify that the transaction failed.
For example, imagine we have the need to atomically increment the value of a key by 1 (let’s suppose Redis doesn’t have INCR).
The first try may be the following:
val = GET mykey val = val + 1 SET mykey $val
This will work reliably only if we have a single client performing the operation in a given time. If multiple clients try to increment the key at about the same time there will be a race condition. For instance, client A and B will read the old value, for instance, 10. The value will be incremented to 11 by both the clients, and finally SET as the value of the key. So the final value will be 11 instead of 12.
Thanks to WATCH we are able to model the problem very well:
WATCH mykey val = GET mykey val = val + 1 MULTI SET mykey $val EXEC
We just have to repeat the operation hoping this time we’ll not get a new race. This form of locking is called optimistic locking and is a very powerful form of locking. In many use cases, multiple clients will be accessing different keys, so collisions are unlikely – usually there’s no need to repeat the operation.
So what is WATCH really about? It is a command that will make the EXEC conditional: we are asking Redis to perform the transaction only if none of the
WATCHed keys were modified. (But they might be changed by the same client inside the transaction without aborting it. More on this.) Otherwise the transaction is not entered at all. (Note that if you WATCH a volatile key and Redis expires the key after you
WATCHed it, EXEC will still work. More on this.)
WATCH can be called multiple times. Simply all the WATCH calls will have the effects to watch for changes starting from the call, up to the moment EXEC is called. You can also send any number of keys to a single WATCH call.
When EXEC is called, all keys are
UNWATCHed, regardless of whether the transaction was aborted or not. Also when a client connection is closed, everything gets
It is also possible to use the UNWATCH command (without arguments) in order to flush all the watched keys. Sometimes this is useful as we optimistically lock a few keys, since possibly we need to perform a transaction to alter those keys, but after reading the current content of the keys we don’t want to proceed. When this happens we just call UNWATCH so that the connection can already be used freely for new transactions.
Using WATCH to implement ZPOP
A good example to illustrate how WATCH can be used to create new atomic operations otherwise not supported by Redis is to implement ZPOP, that is a command that pops the element with the lower score from a sorted set in an atomic way. This is the simplest implementation:
WATCH zset element = ZRANGE zset 0 0 MULTI ZREM zset element EXEC
Redis scripting and transactions
A Redis script is transactional by definition, so everything you can do with a Redis transaction, you can also do with a script, and usually the script will be both simpler and faster.
This duplication is due to the fact that scripting was introduced in Redis 2.6 while transactions already existed long before. However we are unlikely to remove the support for transactions in the short time because it seems semantically opportune that even without resorting to Redis scripting it is still possible to avoid race conditions, especially since the implementation complexity of Redis transactions is minimal.
However it is not impossible that in a non immediate future we’ll see that the whole user base is just using scripts. If this happens we may deprecate and finally remove transactions.