1、复习 MySQl 事务的特性
- 原子性:事务是原子的,它被视为一个不可分割的工作单元。事务中的所有操作要么全部执行成功,要么全部失败回滚,不存在中途状态。原子性确保了数据库的一致性。
- 一致性:事务执行之前,和之后,数据都不能离谱,维护数据库的一致性,和原子性密切相关
- 持久性:一旦事务提交,其结果应该是永久性的(存硬盘),即使在系统发生故障的情况下也不会丢失。数据库的持久性保证了数据的长期存储。
- 隔离性:多个事务可以并发执行,但其结果必须与按某种顺序串行执行的结果一致。隔离性确保一个事务的执行不会被其他事务干扰,通过隔离性可以避免一些并发引起的问题,如脏读(dirty read)、不可重复读(non-repeatable read)和幻读(phantom read)
一致性举例:在一个mysql的事务操作中,事务要么全部执行成功,要么全部失败回滚,不可以有的成功,有的失败,以维护数据库的一致性
2、Redis 事务特性
2.1、原子性(有没有存在争议)
- 最原本的含义,是把多个操作打包到一起,要么全部执行,要么全部不执行
- Redis 确实做到了上述的含义,但如果事务中若干个操作,存在有失败的,那就失败吧,不会有回滚操作
- 相比来说MySQL这里的原子性,走得更远,也是把多个操作打包到一起,要么全都执行成功,要么全都不执行
- 如果mysql的事务中有操作执行失败,则进行回滚!把中间已经执行的操作,全都回退了
- 不得不说,MySQL在这方面成为了“标兵”,提高了“原子性”的门槛
- 这就使人们谈到原子性的时候,更多的是想到的MySQL这种带有回滚的原子性
网上看到,有的人说,redis事务有原子性,有的说没有原子性
- 说有原子性的原因是因为:它确实满足了原子性最基本的含义,将任务打包执行(也只是打包一起执行了)
- 说没有原子性的原因是因为:它没有满足mysql那种事务原子性的操作,他们认为原子性应该是打包一起执行+带有回滚,而明显,redis只是打包一起执行,失败不回滚,这种操作不被他们认为是原子性
2.2、一致性、持久性、隔离性(没有)
不具备一致性:redis没有约束,也没有回滚机制,事务执行过程中如果某个修改操作出现失败,就可能引起不一致的情况
不具备一致性:redis本身就是内存数据库,数据是存储在内存中的。虽然redis也有持久化机制,但是这里的持久化机制,和事务没有什么直接关系
不具备隔离性:redis是一个单线程模型的服务器程序,所有的请求/事务,都是“串行”执行的(多线程涉及隔离性,redis单线程,和隔离性扯不到一起)
Redis 的事务,主要的意义,就是为了“打包”,避免其他客户端的命令,插队插到中间
3、redis事务举生活例子
- 我生日这一天,我和媳妇约好了,晚上出去吃烧烤
- 但是呢,女人嘛,出门是一件麻烦的事情 (化妆...)
- 我就先到了烧烤店,点了牛肉串若干,羊肉串若干,五花肉若干,点完之后,我告诉服务员,“我这人还没齐,你先把单下着,但是先不着急烤”
- 过了一会,我媳妇到了,她又加了一盘烤韭菜,又加了两个烤腰子
- 随后,我告诉服务员:开始考吧!
- 此时,先点的这些肉 和 后点的韭菜、腰子 是一起烤的
- 这两组中间,是没有被插队的
- 这个不插队,不是先抢占位置,而是先让出位置
其实,这就是redis事务一种典型体现,将若干命令打包到一起执行,而且redis事务的执行逻辑也是不立即执行(不抢占位置),下发执行该事务命令后,redis会把当前正在做的事情(比如已经在执行一个事务,会把这个事务做完)做完后,再执行这个事务
4、redis事务的实现方式
- redis中实现事务,是引入了队列(每个客户端都有一个)
- 开启事务的时候,此时客户端输入的命令,就会发给服务器并且进入这个队列中(而不是立即执行)
- 当遇到了“执行事务”命令的时候,此时就会把队列中的这些任务都按照顺序依次执行
- 这些任务都是在redis主线程中完成的,主线程会把事务中的操作都执行完,再处理别的客户端
5、redis的事务为什么搞得这么简单? 为什么不涉及成和MySQL一样强大呢
- MySQL的事务,在背后付出了很大的代价
- 空间上,要花费更多的时间来存储更多的数据
- 时间上,也要有更大的执行开销
- 正是因为MySQL上述的问题,才有了redis上场的机会
- redis想要效率高,必须舍弃掉一些功能
总结: 鱼和熊掌不可兼得!
6、什么时候需要使用到redis的事务呢?以及redis事务的相关逻辑
如果我们把多个操作打包进行,使用事务是比较合适的
比如当我们秒杀商品时,商家放货了5000台,实际如果让5001个人下单成功,就是超卖了
在秒杀商品等情况下就需要redis、的事务了
把每个用户点击购买直到完全下单成功并且商家货物减少这一整个操作合成一个事务进行执行
以前在多线程中,是通过加锁的方式,来避免“插队”的,在redis中就直接使用事务,即可!
逻辑如下:
7、redis事务的具体命令操作
事务三大基本命令 —— 开启、执行、放弃
- 开启事务 —— MULTI
- 执行事务 —— EXEC
- 放弃当前事务 —— DISCARD
- 开启事务(multi)后,可以编写事务中的具体操作,这些操作不会被执行,但会加入到这个事务中,待执行命令EXEC后 ,redis会着手执行该事务,即依次执行该事务中的所有命令语句
- discard表示放弃当前事务,相当于刚才没有进行对事务的操作
redis中的 lua 脚本,也能起到类似于事务的效果
官方网站上说,事务这里的任何能实现的效果,都可以使用 lua 脚本代替
代码举例:
- 当开启事务,并且给服务器发送若干个命令之后,此时服务器重启,此时的这个事务咋办?
- 此时的效果就等同于discard
8、WATCH —— 监控key
watch 监控某个key是否在事务执行之前,发生了改变
刚才的场景中,就可以使用watch命令来监控这个key
看看这个key在事务的 multi 和 exec 之间,set key 之后,是否在外部被其他客户端修改了
watch开启后,在 NULTI 和 exec间另一个 redis客户端对 key进行了修改,此时提交事务,事务中对 key的操作就不会真正被执行!
watch 的实现原理,类似 “乐观锁”
watch 可以监测一个 key,也可以监测多个 key
乐观锁、悲观锁不是指某个具体的锁,而是指的是某一类锁的特性
- 乐观锁:加锁之前,就有一个心理预期,预期接下来锁冲突的概率比较低
- 悲观锁:加锁之前,也有一个心理预期,接下来锁冲突的概率比较高
锁冲突指的是两个线程针对同一个锁加锁,一个能加锁成功,另一个就得阻塞等待
watch的原理:
- 当执行 watch key 的时候
- redis就会给这个key安排一个版本号
- 版本号可以理解成一个“整数”
- 每次在修改的时候,版本号就会“变大”
- 然后在执行 事务 中命令的时候
- 就会做出判定
- 判定这个key的版本号,和最初 watch 的时候
- 记录的版本号是否一致!!!
- 如果一致,说明当前key在事务开启到最终执行这个过程中,没有别的客户端修改,于是才能真正执行这个事务
- 如果不一致,说明key在其他客户端中改过了,因此此处就直接丢弃事务中的操作,exec返回nil(空)
逻辑图及相关解释如下:
简单解释CAS和ABA,若不理解可以私信博主或者自行查询:
- Redis Watch 命令给事务提供check-and-set (CAS) 机制。被Watch的Key被持续监控,如果key在Exec命令执行前有改变,那么整个事务被取消。
ABA问题是CAS机制的缺陷,大概意思是 A(旧值)-->B(新值)-->A(新值) cas乐观锁会认为A没被修改。 但是redis的watch在这种情况下,依然会提示watch key被修改,事务失败。
🧸欢迎您于百忙之中阅读这篇博客,📜希望这篇博客给您带来了一些帮助,祝您生活愉快!