redis版本
- redis版本:3.2.11
事务
-
在关系型数据库中,事务表示一组动作,要么全部执行,要么全部不执行。Redis提供了简单的事务功能,Redis保证一个事务中的所有命令要么都执行,要么都不执行。而一旦客户端发送了EXEC命令,所有的命令就都会被执行,即使此后客户端断线也没关系,因为Redis中已经记录了所有要执行的命令。
-
事务图解
事务命令
-
MULTI
:标记一个事务块的开始,总是返回OK。事务块内的多条命令会按照先后顺序被放进一个队列当中,最后由 EXEC 命令原子性(atomic)地执行 -
DISCARD(丢弃)
:取消事务,放弃执行事务块内的所有命令。如果正在使用WATCH
命令监视某个(或某些) key,那么取消所有监视,等同于执行命令UNWATCH
-
EXEC
:执行事务块内的所有命令。假如某个(或某些) key 正处于WATCH
命令的监视之下,且事务块中有和这个(或这些) key 相关的命令,那么EXEC
命令只在这个(或这些) key 没有被其他命令所改动的情况下执行并生效,否则该事务被打断(abort)。如果客户端在使用MULTI
开启一个事务之后,因为断线而没有成功执行EXEC
,那么事务中的所有命令都不会执行。如果客户端在成功开启事务之后执行EXEC
,则事务中的所有命令都会被执行。 -
WATCH
:WATCH
可以监控一个或者多个key,一旦其中有一个key在EXEC
执行之前被修改(或者删除),那么整个事务都会被取消,EXEC
返回空多条批量回复(null multi-bulk reply)来表示事务已经失败。WATCH
一直持续到EXEC
命令(事务中的命令是在EXEC
之后才执行的,所以在MULTI
命令后可以修改WATCH
监控的key)WATCH
可以为Redis事务提供check-and-set(CAS)行为
-
UNWATCH
:取消WATCH
命令对所有key
的监视。如果在执行WATCH
命令之后,EXEC
命令或DISCARD
命令先被执行了的话,那么就不需要再执行UNWATCH
了。因为EXEC
命令会执行事务,因此WATCH
命令的效果已经产生了。而DISCARD
命令在取消事务的同时也会取消所有对key
的监视,因此这两个命令执行之后,就没有必要执行UNWATCH
了
乐观锁
-
从下面的例子可以看出【客户端1】的事务并没有执行成功,而是执行失败了,因为【客户端2】在【客户端1】执行EXEC之前修改了key的值导致【客户端1】的事务被中断。
客户端1 客户端2 127.0.0.1:6379> WATCH count OK 127.0.0.1:6379> get count (integer) 1 127.0.0.1:6379> incr count (integer) 2 127.0.0.1:6379> multi OK 127.0.0.1:6379> incr count QUEUED 127.0.0.1:6379> EXEC (nil) 127.0.0.1:6379> get count “2” -
如果在
WATCH
执行之后,EXEC
执行之前, 有其他客户端修改了key的值,那么当前客户端的事务就会失败。程序需要做的,就是不断重试这个操作,直到没有发生碰撞为止 -
Redis禁止在
MULTI
和EXEC
之间执行WATCH
命令,而必须在MULTI
之前执行WATCH
命令,WATCH
命令可以被调用多次 -
如果你使用
WATCH
监视了一个带过期时间的键, 那么即使这个键过期了, 事务仍然可以正常执行0. 准备过期的key 127.0.0.1:6379> set username jannal OK 127.0.0.1:6379> EXPIRE username 60 (integer) 1 127.0.0.1:6379> ttl username (integer) 56 1. 监控一个过期的key 127.0.0.1:6379> WATCH username OK 127.0.0.1:6379> get username "jannal" 127.0.0.1:6379> MULTI OK 2. 等待key过期之后再执行这个命令 127.0.0.1:6379> set username jack QUEUED 127.0.0.1:6379> EXEC 1) OK 3. 发现事务执行成功,并且没有过期时间了 127.0.0.1:6379> get username "jack" 127.0.0.1:6379> ttl username (integer) -1
事务执行错误模拟
命令错误
-
比如将set写成sett,这属于语法错误,会造成整个事务无法执行
1. 准备数据 127.0.0.1:6379> set username jannal OK 127.0.0.1:6379> incr count (integer) 1 127.0.0.1:6379> mget username count 1) "jannal" 2) "1" 2. 开启事务 127.0.0.1:6379> multi OK 127.0.0.1:6379> sett username jack (error) ERR unknown command 'sett' 127.0.0.1:6379> incr count QUEUED 表示被服务器缓存到队列里了 127.0.0.1:6379> exec (error) EXECABORT Transaction discarded because of previous errors. 3. 发现username和count的值都为发生变化 127.0.0.1:6379> mget username count 1) "jannal" 2) "1"
运行时错误
-
比如类型错误,比如对字符串进行数学运算
1. 查看执行之前的数据 127.0.0.1:6379> mget username count 1) "jannal" 2) "1" 2.开启事务 127.0.0.1:6379> multi OK 127.0.0.1:6379> set username jack QUEUED 127.0.0.1:6379> incr username QUEUED 127.0.0.1:6379> incr count QUEUED 127.0.0.1:6379> exec 1) OK 2) (error) ERR value is not an integer or out of range 3) (integer) 2 2. 发现事务运行时发生错误,但是后续的incr还是执行成功了 127.0.0.1:6379> mget username count 1) "jack" 2) "2"
持久化
- 当使用
AOF
方式做持久化的时候,Redis
会使用单个 write(2) 命令将事务写入到磁盘中。然而,如果 Redis 服务器因为某些原因被管理员杀死,或者遇上某种硬件故障,那么可能只有部分事务命令会被成功写入到磁盘中。如果 Redis 在重新启动时发现 AOF 文件出了这样的问题,那么它会退出,并汇报一个错误。使用redis-check-aof
程序可以修复这一问题:它会移除 AOF 文件中不完整事务的信息,确保服务器可以顺利启动。
总结
- Redis中的事务仅仅满足了事务不被其他事务打断的权利。Redis的事务没有关系数据库事务提供的回滚(rollback)功能。由于Redis不支持回滚功能,也使得Redis在事务上可以保持简洁和快速。不支持回滚的原因是因为导致redis事务执行失败的两种错误就是命令错误和运行时错误,即只有程序错误才会导致Redis命令执行失败,而这两种错误在生产环境下都不应该出现(在开发和测试阶段会被发现)
- 其中语法错误完全可以在开发时找出并解决,另外如果能够很好地规划数据库(保证键名规范等)的使用,是不会出现如命令与数据类型不匹配这样的运行错误的
事务优化
- Redis事务在发送每一个指令的到事件队列时都要经过一次网络请求,当一个事务内部的指令较多时,需要的网络IO时间也会变长,所以通常Redis的客户端在执行事务时都会结合
Pipeline
一起使用,这样可以将多次IO操作压缩为单次IO操作