文章目录
- Redis 事务
- Redis 的事务和 MySQL 事务的区别:
- 事务操作
- MULTI
- EXEC
- DISCARD
- WATCH
- UNWATCH
- watch的实现原理
- 总结
Redis 事务
什么是事务
Redis 的事务和 MySQL 的事务 概念上是类似的. 都是把⼀系列操作绑定成⼀组. 让这⼀组能够批量执行
Redis 的事务和 MySQL 事务的区别:
- 弱化的原子性: redis 没有 “回滚机制”. 只能做到这些操作 “批量执行”. 不能做到 “⼀个失败就恢复到
初始状态” - 不保证⼀致性: 不涉及 “约束”. 也没有回滚. MySQL 的⼀致性体现的是运行事务前和运行后 , 结果都
是合理有效的, 不会出现中间⾮法状态 - 不需要隔离性: 也没有隔离级别, 因为不会并发执行事务 (redis 单线程处理请求)
- 不需要持久性: 是保存在内存的. 是否开启持久化, 是redis-server 自己的事情, 和事务无关
Redis 事务本质上是在服务器上搞了⼀个 “事务队列”. 每次客户端在事务中进行⼀个操作, 都会把命令先
发给服务器, 放到 “事务队列” 中(但是并不会立即执行)
而是会在真正收到 EXEC 命令之后, 才真正执行队列中的所有操作.
因此, Redis 的事务的功能相比于 MySQL 来说, 是弱化很多的. 只能保证事务中的这⼏个操作是 “连续
的”, 不会被别的客户端 “加塞”, 仅此而已.
事务操作
MULTI
开启⼀个事务. 执行成功返回 OK
EXEC
真正执行事务
每次添加⼀个操作, 都会提示 “QUEUED”, 说明命令已经进入客户端的队列了. 真正执行 EXEC 的时候, 客户端才会真正把上述操作发送给服务器. 此时就可以获取到上述 key 的值了
DISCARD
放弃当前事务,此时直接清空事务队列.,之前的操作都不会真正执行到
WATCH
在执行事务的时候, 如果某个事务中修改的值, 被别的客户端修改了, 此时就容易出现数据不⼀致的问
题
例如
# 客户端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
OK
此时, key 的值是多少呢??
客户端1
客户端2
从输⼊命令的时间看, 是客端1 先执行的 set key 100. 客户端2 后执行的 set key 200.
这个时候,就容易发生歧义,watch 命令就是⽤来解决这个问题的,watch 在该客户端上监控⼀组具体的 key
- 当开启事务的时候, 如果对 watch 的 key 进行修改, 就会记录当前 key 的 “版本号”. (版本号是个简单
的整数, 每次修改都会使版本变大. 服务器来维护每个 key 的版本号情况) - 在真正提交事务的时候, 如果发现当前服务器上的 key 的版本号已经超过了事务开始时的版本号, 就
会让事务执行失败. (事务中的所有操作都不执行).
客户端1先执行
然后客户端2执行
然后再回到客户端1执行
此时可以发现事务已经被取消了. 这次提交的所有命令都没有执行
# 客户端1
127.0.0.1:6379> watch key # 开始监控 key
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set key 100 #进行修改,从服务器获取key的版本号0,记录key的版本号
QUEUED
127.0.0.1:6379> set key2 200
QUEUED
# 客户端2
127.0.0.1:6379> set key 200 # 修改成功, 使服务器端的 k1 的版本号 0 -> 1
OK
# 客户端1
127.0.0.1:6379> exec # 真正执⾏修改操作, 此时对⽐版本发现, 客⼾端的 k1 的版本号是 0, 服务器上的版本号是 1, 版本不⼀致! 说明有其他客⼾端在事务中间修改了k1
(nil)
127.0.0.1:6379> get key
"200"
127.0.0.1:6379> get key2 # 事务已经被取消
(nil)
UNWATCH
取消对 key 的监控,相当于 WATCH 的逆操作
watch的实现原理
watch的实现,类似于一个“乐观锁”
乐观锁,悲观锁不是指某个具体的锁,而是指的是某一类锁的特性
乐观锁:加锁之前,就有一个心理预期,预期接下来锁冲突的概率比较低
悲观锁:加锁之前,也有一个心理预期,接下来锁冲突的概率比较高
锁冲突概率高,和冲突概率低,接下来要做的工作是不一样的
- 当执行watch key的时候,就会给这个key安排一个版本高,版本号可以理解成一个“整数”,每次在修改的时候,版本号都会“变大”
- 在执行exec时,就会做出判定,判定当前这个key的版本号,和最初watch的时候记录的版本号是否一致,如果一致,说明当前key在事务开启到最终执行的这个过程中,没有别的客户端修改,于是才能真正进行设置,如果不一致,说明key在其他客户端改过了,因此此处就直接丢弃事务中的操作,exec返回nil
总结
Redis的事务,要比mysql的事务简单的多
- 原子性:Redis的事务,并不支持回滚
- 一致性:Redis并不会保证事务执行前和执行后,内容统一
- 持久性:Redis主要通过内存来存储数据
- 隔离性:Redis自身作为一个单线程的服务器模型,上面的请求本质上都是串行执行的