[Redis][事务]详细讲解
目录
- 0.什么是事务?
- 1.Redis 事务本质
- 2.Redis 事务意义
- 3.事务操作
- 1.MULTI
- 2.EXEC
- 3.DISCARD
- 4.WATCH
- 5.UNWATCH
0.什么是事务?
- Redis的事务和MySQL的事务概念上是类似的,都是把一系列操作绑定成一组,让这一组能够批量执行
- Redis事务和MySQL的区别:
- 弱化的原子性:Redis没有”回滚机制”,只能做到这些操作”批量执行”,不能做到”一个失败就恢复到初始状态”
- 如果事务中若干个操作,存在有失败的,那就失败吧,不会有回滚操作
- 不保证一致性:不涉及”约束”,也没有回滚,事务执行过程中如果某个修改操作出现失败,就可能引起不一致的情况
- MySQL的一致性体现的是运行事务前后运行后,结果都是合理有效的,不会出现中间非法状态
- 不需要隔离性:也没有隔离级别,因为不会并发执行事务(Redis单线程处理请求)
- 不需要持久型:是保存在内存的,是否开启持久化,是
redis-server
自己的事情,和事务无关
- 弱化的原子性:Redis没有”回滚机制”,只能做到这些操作”批量执行”,不能做到”一个失败就恢复到初始状态”
- Redis如果按照集群模式部署,就不支持事务
1.Redis 事务本质
- Redis事务**本质**上是在服务器上搞了一个”事务队列”,每次客户端在事务中进行一个操作,都会把命令先发给服务器,放到”事务队列”中(但是不会立即执行),而是会在真正收到
EXEC
命令之后,才真正执行队列中的所有操作- 因此,Redis事务的功能相比于MySQL来说,是弱化很多的,只能保证事务中的这几个操作是”连续的”,不会被别的客户端”加塞”,仅此而已
- 综上:
- Redis事务的意义,就是为了"打包",避免其他客户端的命令,插队到中间
- 此处的不被插队,不是先抢占位置,而是先让出位置
2.Redis 事务意义
- Redis的事务为啥就搞的这么简单,为啥不设计成和MySQL一样强大呢?
- MySQL的事务,在背后付出了很大的代价
- 空间上,要花费更多的空间来存储更多的数据
- 时间上,也要有更大的开销
- 正是因为MySQL有上述的问题,才有了Redis的用武之地
- MySQL的事务,在背后付出了很大的代价
- 什么时候需要使用到Redis事务呢?
- 需要把多个操作打包进行,使用Redis事务比较合适
- 示例:商品抢购
- 典型写法如果不加上任何限制,就可能会出现线程安全问题
- 在多线程中,通过加锁的方式,来避免"插队"
if(count > 0) { // 下单成功 count-- }
- 在Redis中直接使用事务即可
# 开启事务 get count if count > 0 decr count # 执行事务 # Redis服务器收到执行事务操作的时候,才会真正执行
- 典型写法如果不加上任何限制,就可能会出现线程安全问题
- 说明:
- Redis原生命令中不支持条件判定,但是Redis支持lua脚本,可以通过lua实现
- 可以认为lua脚本的实现方式是Redis事务的进阶版本
3.事务操作
1.MULTI
- 功能:开启一个事务,执行成功返回OK
- 当开启事务,并且向服务器发送若干个命令之后,此时服务器重启,此时的这个事务怎么办?
- 此时的效果相当于
DISCARD
- 此时的效果相当于
2.EXEC
- 功能:真正执行事务
- 每次添加一个操作,都会提示”
QUEUED
”,说明命令已经进入服务端的队列了 - 真正执行
EXEC
的时候,服务器才会真正执行命令
- 每次添加一个操作,都会提示”
- 示例:
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
127.0.0.1:6379> get k1
"1"
127.0.0.1:6379> get k2
"2"
127.0.0.1:6379> get k3
"3
3.DISCARD
- 功能:放弃当前事务,此时直接清空事务队列,之前的操作都不会真正执行到
- 示例:
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)
4.WATCH
- 情景:在执行事务的时候,如果某个事务中修改的值,被别的客户端修改了,此时就容易出现数据不一致的问题
- 此时
key
的值是多少呢?- 从输入命令的时候看,是客户端1先执行的
set key 100
,客户端2后执行的set key 200
,但是从实际的执行时间看,是客户端2先执行的,客户端1后执行的# 客⼾端 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 # 结果查询 127.0.0.1:6379> get key "100"
- 这个时候,其实很容易引起歧义
- 因此,即使不保证严格的隔离性,至少也要告诉用户,当前的操作可能存在风险
- 从输入命令的时候看,是客户端1先执行的
watch
命令就是用来解决这个问题的,watch
在该客户端上监控一组具体的key
- 当开启事务的时候,如果对
watch
的key
进行修改,就会记录当前key
的”版本号” - 在真正提交事务的时候,如果发现当前服务器上的
key
的版本号已经超过了事务开始时的版本号,就会让事务执行失败
- 当开启事务的时候,如果对
watch
本质上是给exec
加了个判定条件,属于"乐观锁"- 示例:
# 客户端1先执行 127.0.0.1:6379> watch k1 OK 127.0.0.1:6379> MULTI OK 127.0.0.1:6379> set k1 100 QUEUED 127.0.0.1:6379> set k2 1000 QUEUED # 只是入队列,但是不提交事务执行 # 客户端2后执行 127.0.0.1:6379> set k1 200 OK # 客户端1再执行 127.0.0.1:6379> EXEC (nil) 127.0.0.1:6379> get k1 "200" 127.0.0.1:6379> get k2 (nil) # 此时说明事务已经被取消了,这次提交的所有命令都没有执行
5.UNWATCH
- 功能:取消对
key
的监控,相当于WATCH
的逆操作