认识redis事务
说起事务,会不由自主地想起MySQL中的事务,MySQL中的事务有四个性质:
①原子性:把多个操作,打包成一个整体,要么全都执行成功,要么全都不执行。 ②一致性:事务执行之前和执行之后,数据能够对得上号。 ③持久性:事务中做出的修改会存在硬盘中 ④隔离性:事务并发执行,涉及到的各类读提交问题。
相较于MySQL事务,redis事务很简单:
①原子性:redis原子性是指将多个操作打包在一起,要么全都执行,要么全都不执行。注意:这里跟MySQL事务中的原子性相比,redis原子性不管这些操作有没有成功,它不管!如果事务中有些操作失败了,redis说失败就失败吧。而MySQL则不行,一旦有操作失败,则全部回滚!(有部分观点任务,redis没有原子性,因为以MySQL事务的原子性作为标杆,原子性必须要么执行成功,要么不执行)
②不具备一致性:MySQL一致性是体现事务在执行前和执行后都是合理有效的,没有中间非法状态,而redis没有约束,没有回滚机制,因此事务执行过程中,某个操作失败,则可能会出现不一致的情况。
③不需要隔离性:Redis是一个单线程模型的服务器程序,所有请求/事务,都是"串行"执行的
④不需要持久性:redis数据是保存在内存的.是否开启持久化,是redis-server自己的事情,和事务⽆关。
Redis事务本质就是在服务器上的一个"事务队列"(每个客户端都有一个这样的队列),客户端在事务中进行一个操作,本质就是把命令发送给服务器,放到事务队列中,但是不好立即执行,而是在主线程收到EXEC命令后,主线程才去将队列中的操作依次执行,因此,Redis事务的意义,便是:避免客户端后来的命令插队,并不会去保证执行得对不对。
那么为什么redis事务,不去设计得向MySQL事务那样强大呢?
MySQL事务的强大,其背后是付出了巨大的代价的:
比如,想要实现原子性,一致性,需要实现回滚,那么需要额外开辟空间,去保存相关日志,像dolog等,再然后是每一条记录要搞个隐藏列,通过隐藏列去记录各种信息。因此空间上需要开销很大,时间上执行也有代价。redis正是因为MySQL的空间时间开销大的问题,才制定出来的。
而redis事务的场景:超卖问题。
操作事务
开启事务
MULTI
开启一个事务,执行成功返回OK。
执行事务
EXEC
真正执行事务:
代码语言:javascript复制127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> set k1 1
QUEUED
127.0.0.1:6379> set k2 2
QUEUED
127.0.0.1:6379> set k3 3
QUEUED
127.0.0.1:6379> EXEC
1) OK
2) OK
3) OK
每次添加⼀个操作,都会提示"QUEUED",说明命令已经进⼊客户端的队列了.真正执行EXEC的时候,客户端才会真正把上述操作发送给服务器.此时就可以获取到上述key的值了。
代码语言:javascript复制127.0.0.1:6379> get k1
"1"
127.0.0.1:6379> get k2
"2"
127.0.0.1:6379> get k3
"3"
放弃当前事务
DISCARD
放弃当前事务,此时直接清空事务队列,之前的操作都不会去执行。
代码语言:javascript复制127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> set k1 1
QUEUED
127.0.0.1:6379> set k2 2
QUEUED
127.0.0.1:6379> DISCARD
OK
127.0.0.1:6379> get k1
(nil)
127.0.0.1:6379> get k2
(nil)
监控
WATCH
在执行事务的时候,如果某个事务中修改的值,被别的客户端修改了,此时就容易出现数据不一致的问题:
代码语言:javascript复制# 客⼾端1 先执⾏
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> set key 100
QUEUED
# 客⼾端2 再执⾏
127.0.0.1:6379> set key 200
OK
# 客⼾端1 最后执⾏
127.0.0.1:6379> EXEC
1) OK
此时的key是多少呢?
从输入命令的时间看,是客户端1先执⾏的setkey100.客户端2后执的setkey200.但是从实际的执行时间看,是客户端2先执⾏的,客户端1后执行的。
代码语言:javascript复制127.0.0.1:6379> get key
"100"
这个时候,其实就容易引起歧义.因此,即使不保证严格的隔离性,至少也要告诉用户,当前的操作可能存在风险。
watch命令就是用于解决这个问题的。watch在该客户端上监控⼀组具体的key。
• 当开启事务的时候,如果对watch的key进行修改,就会记录当前key的"版本号"。(版本号是个简单的整数,每次修改都会使版本变大。服务器来维护每个key的版本号情况) • 在真正提交事务的时候,如果发现当前服务器上的key的版本号已经超过了事务开始时的版本号,就会让事务执行失败。(事务中的所有操作都不执行)
示例
客户端1先执行:
代码语言:javascript复制27.0.0.1:6379> watch k1 # 开始监控 k1
OK
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> set k1 100 # 进⾏修改, 从服务器获取 k1 的版本号是 0. 记录 k1 的版本
QUEUED
127.0.0.1:6379> set k2 1000
QUEUED
客户端2后执行:
代码语言:javascript复制127.0.0.1:6379> set k1 200 # 修改成功, 使服务器端的 k1 的版本号 0 -> 1
OK
客户端1再执行:
代码语言:javascript复制127.0.0.1:6379> EXEC #真正执⾏修改操作,此时对⽐版本发现,客⼾端的k1的版本不一致,返回空
(nil)
127.0.0.1:6379> get k1
"200"
127.0.0.1:6379> get k2
(nil)
此时说明事务已经被取消了.这次提交的所有命令都没有执行。
UNWATCH--取消监控。