Redis篇5-redis事务
概述
- 官方说明:https://redis.io/topics/transactions
- redis“部分”支持事务(部分回滚)
- 关键命令
- MULTI 开始事务
- EXEC 开始执行事务内命令s
- DISCARD 取消事务并放弃事务内命令s的执行
- WATCH 监视一个或多个key,开始乐观锁CAS的事务操作
- UNWATCH 取消所有key监视
- 从Redis2.2开始支持用于乐观锁的check-and-set (CAS)
开始使用
multi-exec正常提交
mset k1 v1 k2 v2 k3 v3
multi
set k1 vv1
get k1
set k4 v4
get k4
exec
keys *
multi-exec异常1-注意
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set k5 v5
QUEUED
127.0.0.1:6379> set k1 11 12
QUEUED
127.0.0.1:6379> exec
1) OK
2) (error) ERR syntax error
127.0.0.1:6379> get k5
"v5"
127.0.0.1:6379> get k1
"vv1"
127.0.0.1:6379> keys *
1) "k5"
2) "k1"
3) "k2"
4) "k3"
5) "k4"
可以看出,multi后加入事务命令OK,但exec执行时出错时,并没有全部回滚。。
multi-exec异常2-说redis“部分支持事务”的原因
127.0.0.1:6379> flushdb
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set k1 v1
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> getset k3
(error) ERR wrong number of arguments for 'getset' command
127.0.0.1:6379> getset k3 v3
QUEUED
127.0.0.1:6379> exec
(error) EXECABORT Transaction discarded because of previous errors.
127.0.0.1:6379> keys *
(empty list or set)
可以看出,exec之前出错,整个事务回滚,即DISCARD。
主动 DISCARD
127.0.0.1:6379> flushdb
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set k1 v1
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> get k2
QUEUED
127.0.0.1:6379> discard
OK
127.0.0.1:6379> keys *
(empty list or set)
discard比较好理解,就是主动退出/回滚事务,取消命令的执行。
乐观锁-WATCH-CAS
- 乐观锁一般相对于悲观锁理解,优点是不阻塞,缺点是失败率变高
- 事务开始前(multi),watch目标key,如果提交前目标key被其他会话改动(即CAS过程),则事务回滚(DISCARD)
- client1,session1
127.0.0.1:6379> get k1
"vv1"
127.0.0.1:6379> watch k1
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set k1 0v1
QUEUED
127.0.0.1:6379> get k1
QUEUED
127.0.0.1:6379> exec
(nil)
127.0.0.1:6379> get k1
"v1"
127.0.0.1:6379> get k1
"v1"
127.0.0.1:6379> get k2
"v2"
127.0.0.1:6379> watch k1
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set k1 vv1
QUEUED
127.0.0.1:6379> set k2 vv2
QUEUED
127.0.0.1:6379> exec
(nil)
127.0.0.1:6379> mget k1 k2
1) "vvvv1"
2) "v2"
- client2,session2,在session1 watch k1后exec前执行以下
127.0.0.1:6379> get k1
"vv1"
127.0.0.1:6379> set k1 v1
OK
127.0.0.1:6379> set k1 vvvv1
OK
127.0.0.1:6379> get k1
"vvvv1"
可以看到结果是全部回滚。
关于redis“部分支持事务”
由上面使用可以看出redis事务并不像传统数据库事务那么“纯粹”——出错便全体回滚。
大部分场景都是“部分事务回滚”,表现有点类似spring配置事务传播行为的 RequiredNew。
redis事务操作出错是否全部回滚,本身也“看情况”
- exec之前出错
multi到exec之间可以看成是批量收集命令的过程,这个过程中出错(没有QUEUED),exec执行就会全体回滚,相当于主动DISCARD,比如命令的语法错误。 - exec之后出错
命令全部收集OK了(QUEUED),此时出错则不会全体回滚,比如对键做了错误操作,类似spring配置事务传播行为的 RequiredNew,提交一个算一个。 - 官方关于为什么不支持传统回滚的原因(说白了就是redis很快,不需要)
https://redis.io/topics/transactions#why-redis-does-not-support-roll-backsWhy Redis does not support roll backs?
当然,使用 watch-multi-cas-exec 不一样,这会DISCARD,代表全部回滚。
综上,结合与传统数据库事务特点的对比,redis“部分支持事务”:
- exec执行与提交
redis在exec之前,只是先攒着命令,并没有执行过(传统的数据库是执行了,事后通过undo日志来回滚),exec之后就是批量命令顺序执行,执行多少就多少,提交一个算一个。 - 不保证原子性
即上面说的,exec之后,“事务”中的命令,如果有一个出错,其后的命令会继续执行,不受影响。exec之前的话,有点像我们进行编程时的操作前校验,“校验”(指加入命令队列)不通过,便不再继续,也就是redis所谓的回滚(DISCARD)。