Redis:事务
- 事务
- 事务操作
- MULTI & EXEC
- DISCARD
- WATCH
事务
在MySQL
中,事务遵循CIRD
特性:
原子性
:事务是一个整体,要么没有发生,要么已经执行完毕一致性
:事务执行前后,数据都要符合业务要求隔离性
:事务并发时,各个事务之间的可见性受到一定限制持久性
:事务做出的修改会被存储到硬盘中持久化
由于MySQL
是支持并发的,所以MySQL
的事务机制非常复杂,要考虑很多情况。但是Redis
不同,作为一个单线程模型的数据库,Redis
根本就没有并发问题,所以Redis
的事务机制会简单很多。
Redis
的事务特点如下:
弱化原子性
:在Redis
中,没有回滚机制,不能做到事务中一个命令出错,回滚到事务开始前,简单理解来说,Redis
的事务就是一个简单的打包执行无需隔离性
:由于Redis
是单线程模型,不会出现并发,不会有多个事务同时进行,所以不需要隔离性无需持久性
:Redis
是内存级别的数据库,虽然有持久化机制,但是事务执行完毕后,不能保证数据被写入硬盘不保证一致性
:在MySQL
中,一致性是通过前三个特性都满足进而达成的,很不巧Redis
都不满足,自然也就不能保证一致性了
既然如此,Redis
的事务有啥用?
其实Redis
的事务就像一个简单的打包,保证一个事务内部的指令是连续执行的。就像在排队时,几个好朋友总要连着站在一起,不让别人插在中间。
假设排队的时候,先到了几个人,还有几个人没来,此时如果占着排队的位置,让后来的朋友直接插入,貌似有点不道德了。所以一种处理策略是,在朋友到来之前,让后面的人排到前面去,等到朋友来了,几个人再一起插入队尾。
也就是说,Redis
的事务策略,不是先抢占位置,而是先空出位置给别的事务,等到自己一个事务内部的所有操作都齐全了,再一起执行。
事务操作
MULTI & EXEC
multi
:开启事务exec
:提交事务
示例:
开启事务后,不论输入什么指令,都返回QUEUE
,此时会在客户端维护一个队列,将所有指令入队列。最后执行exec
提交事务,所有的指令再同时返回。
DISCARD
discard
:取消事务
示例:
开启事务后,输入两个值,再通过discard
取消事务,最后查询key1
,发现插入失败,因为这个请求没有提交给客户端,而是直接被取消了。
另外的,如果在事务执行的过程中,Redis
崩溃,恢复后效果和discard
一样,就是事务内的操作都取消了。
WATCH
现有以下场景:
序号 | 客户端1 | 客户端2 |
---|---|---|
1 | multi | |
2 | set key 111 | |
3 | set key 222 | |
4 | exec | |
5 | get key |
最后客户端get key
返回值是多少?
这是一个很常见的事务问题,虽然客户端1
执行set key
更早,但是它提交事务更晚,最终客户端
的结果其实是111
。
在事务执行过程中,客户端1
无法感知外部的变化,导致客户端2
拿不到自己想要的数据,被其他事务覆盖了。
watch
:让事务可以监听外部的变化,如果监听的数据被外部改变,操作失效
语法:
watch key [key ...]
示例:
左侧的终端,在执行事务前开启了watch key1
,开启事务后set key1 111
。在事务提交前,右侧终端修改了key1 222
,随后左侧终端提交事务时,返回了nil
表示事务操作无效。
因为watch
监听到了key1
被外部修改,此时自己的事务提交可能会影响其它客户端,于是取消该操作。
如果想要取消watch
,可以使用unwatch
指令:
unwatch
这个指令没有参数,一次性取消所有key
的监听。
watch
使用了一种版本号的机制,每个数据都有一个版本号,每次修改key
的值时,都会修改其版本号。在watch key
时,会记录当前的版本号。在事务提交时,检测当前的版本号是否与之前的版本号相同,如果相同那么提交成功,如果版本号不同,说明有别的用户修改了数据,导致版本号修改,事务就直接被丢弃了。