事务特性
单个 Redis 命令的执行是原子性的,但 Redis 没有在事务上增加任何维持原子性的机制,所以 Redis 事务的执行并不是原子性的。
事务可以理解为一个打包的批量执行脚本,但批量指令并非原子化的操作,中间某条指令的失败不会导致前面已做指令的回滚,也不会造成后续的指令不做。
事务执行基本流程
一个事务从开始到执行会经历以下三个阶段:
- 开始事务。
- 命令入队。
- 执行事务。
事务常用命令
multi: 标记事务的开启
exec :执行事务
discard:取消事务,放弃执行事务块内的所有命令。
watch key :监视key,在一个事务内监视了某些key,如果在执行事务之前这个key的值有被其他命令改变,那么本次事务中对这个key的操作就会失败
unwatch:取消对所有key的监视。
事务执行
异常:
编译型异常:(入队时会提示命令错误),该事务的所有命令都不会被执行。
例子如下:事务中有一条命令入队时提示错误了,最后执行事务的时候整个事务都会执行失败。
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> g 1
(error) ERR unknown command `g`, with args beginning with: `1`,
127.0.0.1:6379(TX)> get name
QUEUED
127.0.0.1:6379(TX)> get age
QUEUED
127.0.0.1:6379(TX)> exec
(error) EXECABORT Transaction discarded because of previous errors.
127.0.0.1:6379>
运行时异常:(入队时没有错误,但是执行的时候会出现错误),只是出现异常的命不会被执行,其他命令依旧执行。
例子如下:可以看到命令都是成功入队,但是执行的时候会有一条命令执行失败(原因是字符串不能执行+1操作),但是不影响正确命令的执行。
127.0.0.1:6379> set name ljh
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> set age 22
QUEUED
127.0.0.1:6379(TX)> incrby name 1
QUEUED
127.0.0.1:6379(TX)> exec
1) OK
2) (error) ERR value is not an integer or out of range
127.0.0.1:6379>
乐观锁:
Redis的watch监视器的作用其实就相当于乐观锁(乐观锁机制认为资源不会被人修改,所以默认不加锁,而是用一个version字段来标识这条数据是否被别人修改过,如:用0表示没有被修改,1表示被修改),watch就是用来监视数据有没有被修改的,在Redis事务中,如果监视的数据被修改过,那么本次事务会执行失败,需要重新监视数据,然后重新执行事务。
例:
设置账户1有100元,账户二有900元,然后监视账户1:
127.0.0.1:6379> set money1 100
OK
127.0.0.1:6379> set money2 900
OK
127.0.0.1:6379> watch money1
然后开启一个事务用于转账:账户2向账户1转账200元:(先不提交事务)
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> incrby money1 200
QUEUED
127.0.0.1:6379(TX)> decrby money2 200
QUEUED
开启另外一个客户端,模拟别人修改账户1的金额为1000元:
127.0.0.1:6379> set money1 1000
OK
最后提交转账事务:
127.0.0.1:6379(TX)> exec
(nil)
结果:返回nil说明事务执行失败。因为在转账事务中还没有执行就先被别人抢先修改了数据。
解决方法是:重新监视数据,再一次执行事务,直到成功为止。