Redis事务

Redis事务

 

基础

事务
可以一次执行多个命令,本质是一组命令的集合。一个事务中的所有命令都会序列化,按顺序地串行化执行而不会被其它命令命令插入,不许加密。

一个队列中,一次性、顺序性、排他性的执行一系列命令。
常用命令
DISCARD
取消事务,放弃执行事务块内的所有命令
EXEC
执行所有事务块内的命令
MULTI
标记一个事务块的开始
UNWATCH
取消WATCH命令对所有key的监控
WATCH key [key......]
监视一个(或多个)key,如果在事务执行之前这个(或这些)key被其他命令所改动,那么事务将被打断
Redis与Mysql事务对比

Redis            Mysql
开启                Start transaction  MULTI
语句                普通sql            普通命令
失败                Rollback回滚        DISCARD取消
成功                Commit            EXEC

事务中的几种情况

正常执行:
127.0.0.16379>MULTI
ok
127.0.0.16379>Set k1 v1
QUEUED
127.0.0.16379>Set k2 v2
QUEUED
127.0.0.16379>get k2
QUEUED
127.0.0.16379>set k3 v3
QUEUED
127.0.0.16379>EXEC
1)OK
2)OK
3)“v2”
4)OK
放弃事务:
127.0.0.16379>MULTI
ok
127.0.0.16379>Set k1 11
QUEUED
127.0.0.16379>Set k2 22
QUEUED
127.0.0.16379>set k3 33
QUEUED
127.0.0.16379>DISCARD
OK
全体连坐:
127.0.0.16379>MULTI
ok
127.0.0.16379>set k1 v1
QUEUED
127.0.0.16379>set k2 v2
QUEUED
127.0.0.16379>set k3 v3
QUEUED
127.0.0.16379>getset k3
(error)ERR wrong number of arguments for “getset” comand
127.0.0.16379>EXEC
(error)EXECABORT Transaction disca
冤头债主:
127.0.0.16379>MULTI
ok
127.0.0.16379>incr k1
QUEUED
127.0.0.16379>set k2 v2
QUEUED
127.0.0.16379>set k3 v3
QUEUED
127.0.0.16379>set k4 v4
QUEUED
127.0.0.16379>EXEC
1)(error) ERR value is not an integer or out of range
2)OK
3)OK
4)OK

思考

张三正在买票

Ticket-1money-100

而票只有一张,如果在我MULTI之后、EXEC之前,票被别人买走了--ticket变成0

我该如何观察这种情况,并不再提交。

悲观的想法:

世界充满危险,肯定有人和我抢,给ticket上锁,只有一个人能操作【悲观锁】

乐观的想法:

没有那么多人和我抢,我只需要注意有没有人更改ticket的值就可以了【乐观锁】

Redis的事务中,启用的是乐观锁,只负责监测key有没有改动

 
127.0.0.16379>set ticket 1
OK
127.0.0.16379>set zhangsan 300
OK
127.0.0.16379>set lisi 500
OK
127.0.0.16379>watch ticket
OK
127.0.0.16379>MULTI
OK
127.0.0.16379>decrby zhangsan 100
QUEUED
127.0.0.16379>decr ticket
QUEUED
127.0.0.16379>EXEC
(nil)//返回nil说明监视的ticket已经改变了,事务就取消了
127.0.0.16379>get ticket
“0127.0.0.16379>get ticket
“1127.0.0.16379>decr ticket
(integer) 0

WATCH|UNWATCH

我们再来聊聊悲观锁/乐观锁/CAS(Check And Set)

悲观锁(Pessimistic Lock),顾名思义,就是很悲观,每次去拿数据的时候都认为别人修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁,传统的关系型数据库里面就用到了很多这种锁机制,比如行锁,表锁、写锁、读锁等,都是在做操作之前先上锁。

乐观锁(Optimistic Lock),顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可用使用版本号等机制。乐观锁适用于多读的应用类型,这样可用提高吞吐量。
乐观锁策略:提交版本必须大于记录当前版本才能执行更新。
初始化信用卡可用余额和欠额
127.0.0.16379>set balance 100
OK
127.0.0.16379>set debt 0
OK

WATCH过程中无篡改

无加塞篡改,先监控再multi
保证两笔金额变动再同一个事务内
127.0.0.16379>WATCH balance 
OK
127.0.0.16379>MULTI
OK
127.0.0.16379>decrby balance 20
OK
127.0.0.16379>incrby debt 20
OK
127.0.0.16379>EXEC
1)(integer) 80
2)(integer)20

WATCH过程中有篡改

客户端1
127.0.0.16379>WATCH balance 
OK
127.0.0.16379>MULTI
OK
127.0.0.16379>decrby balance 20
OK
127.0.0.16379>incrby debt 20
OK
127.0.0.16379>EXEC
(nil)
127.0.0.16379>get balance
‘800
客户端2
127.0.0.16379>get balance
‘80127.0.0.16379>set balance 800
OK

WATCH+UNWATCH+篡改

客户端1
127.0.0.16379>get balance
“800127.0.0.16379>get debt
“20127.0.0.16379>WATCH balance
OK
127.0.0.16379>set balance 500
OK
127.0.0.16379>UNWATCH #上面修改,则放弃监控
OK
127.0.0.16379>WATCH balance
OK
127.0.0.16379>MULTI
OK
127.0.0.16379>set balance 80
QUEUED
127.0.0.16379>set debt 20
QUEUED
127.0.0.16379>EXEC
1)OK
2)OK
客户端2
127.0.0.16379>get balance #第一次查看:放弃监控的时候查询
“500127.0.0.16379>get balance  #第二次查看:EXEC
“80

注意:一旦执行了exec之前加的监控锁都会被取消

小结

Watch命令,类似乐观锁,事务提交时,如果key的值已被别的客户端改变,比如某个list已被别的客户端push/pop过了,整个事务队列都不会执行。

通过watch命令在事务执行之前监控多个keys,倘若在WATCH之后有任何key的值发生了变化,EXEC命令执行的事务都将被放弃,同时返回Nullmulti-bulk应答以通知调用者事务执行失败。

3阶段

开启:以MULTI开始一个事务

入队:将多个命令入队到事务,接到这些命令并不会立即执行,而是放到等待执行的事务队列里面

执行:由EXEC命令触发事务

3特性

1.单独的隔离操作:事务中的所有命令都会被序列化,按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求打断。

2.没有隔离级别的概念:队列中的命令没有提交之前都不会实际的被执行,因为事务提交前任何指令都不会被实际执行,也就不存在“事务内的查询要看到事务里的更新”,在事务外查询不能看到”这个让人万分头疼的问题。

3.不保证原子性:redis同一个事务如果有一条命令执行失败,其后的命令仍然会被执行,没有回滚。

原文地址:https://www.cnblogs.com/-wenli/p/10934308.html