[Redis] Redis事务

🌸个人主页:https://blog.csdn.net/2301_80050796?spm=1000.2115.3001.5343

🏵️热门专栏:

🧊 Java基本语法(97平均质量分)https://blog.csdn.net/2301_80050796/category_12615970.html?spm=1001.2014.3001.5482

🍕 Collection与数据结构 (92平均质量分)https://blog.csdn.net/2301_80050796/category_12621348.html?spm=1001.2014.3001.5482

🧀线程与网络(96平均质量分) https://blog.csdn.net/2301_80050796/category_12643370.html?spm=1001.2014.3001.5482

🍭MySql数据库(93平均质量分)https://blog.csdn.net/2301_80050796/category_12629890.html?spm=1001.2014.3001.5482

🍬算法(97平均质量分)https://blog.csdn.net/2301_80050796/category_12676091.html?spm=1001.2014.3001.5482

🍃 Spring(97平均质量分)https://blog.csdn.net/2301_80050796/category_12724152.html?spm=1001.2014.3001.5482

🎃Redis(97平均质量分)https://blog.csdn.net/2301_80050796/category_12777129.html?spm=1001.2014.3001.5482

🐰RabbitMQ(97平均质量分) https://blog.csdn.net/2301_80050796/category_12792900.html?spm=1001.2014.3001.5482

感谢点赞与关注~~~

目录

  • [1. Redis事务特性](#1. Redis事务特性)
  • [2. Redis事务的意义](#2. Redis事务的意义)
  • [3. Redis事务实际操作](#3. Redis事务实际操作)

1. Redis事务特性

我们在之前学习MySQL和Spring的时候曾经接触过事务,我们知道,事务就是把好几个操作打包成为一个操作一起执行.这里的Redis的事务也是相同的道理.

我们在之前学习MySQL的事务的时候,我们知道MySQL的事务有4个特性,分别是原子性,一致性,持久性,隔离性.原子性就是把多个操作打包成为一个操作,要不都执行成功,要不都执行失败,一致性就是事务执行之前和执行之后数据都是合法的, 不可以出现某种中间的情况.持久性就是把数据保存在硬盘中,隔离性涉及到了事务的并发执行,隔离分为4个级别,分别是脏读,不可重复读,幻读,串行化.

但是Redis相比于MySQL的事务,就显得非常逊色.

  • 首先原子性,Redis的事务,是否存在原子性,存在争议.Redis的事务虽然和MySQL一样,都是把多个操作打包为了一个操作.但是MySQL的事务在其中的操作出现一些异常的时候,数据都会回滚回去.要不全都执行成功,要不全部执行不成功.但是Redis中的事务如果有操作执行失败的时候,不具有回滚机制.也就是Redis的事务不具有要不都执行成功,要不都执行不成功的特性.Redis的这个原子性也就相当于MySQL原子性的一个半成品.
  • 其次一致性,由于Redis没有约束,也没有回滚机制,如果事务执行过程中某个操作出现了失败,就可能引起数据不一致的情况.
  • 不具备持久性,Redis本身就是内存数据库,数据是存在内存中的.虽然事务具有持久化的机制,但是这和事务没有关系.
  • 最后不具备隔离性,Redis是一个单线程模型的服务程序,不存在并发的过程,所有的请求/事务都是串行化执行的.

2. Redis事务的意义

这么多特性Redis都不具备,那么Redis的事务到底有什么意义呢?

Redis的事务最主要的意义就是为了"打包",避免其他客户端的命令插队到中间.

Redis实现事务的时候引入了队列 .每个客户端都有一个队列.开启事务的时候,此时客户端输入的命令就会发个服务器并且进入了这个队列中,而不是立即执行.当遇到"执行事务"的命令的时候,此时就会把队列中的这些任务都按照顺序依次执行 .执行这些事务的时候是在Redis主线程中完成的.主线程会把事务中的操作都执行完,再处理别的客户端.

虽然Redis事务的功能没有MySQL强大,但是MySQL的事务为了完成这些强大的功能,也付出了不小的代价,空间上,需要花费更多的空间来存储更多的数据.时间上,也需要有更大的执行开销.

正是因为MySQL的事务有上述的问题,才有了Redis上场的机会.

比如下面的场景:

我们需要在购物网站上秒杀一个东西,就有可能出现超卖的情况,就是商家放了5000个商品,下单成功了5001个就是超卖.为了避免这样的情况,我们在多线程的章节,曾经通过上锁的方式来避免这样的情况.如果我们引入了Redis的话,我们可以直接使用事务即可解决.

比如两个客户端都在临界情况下下了单.

当客户端1开启一个事务的时候,事务1就会被放入到Redis的事务队列中,当客户端2也开启了一个事务的时候,事务2也会被放到队列中.事务1收到执行事务的命令被执行之后,count--,在事务2执行的时候,count>0的条件就不满足了,所以事务2就会执行失败.

上面的案例中,虽然Redis不具有if判断条件这样编程语言的功能.但是Redis可以引入lua脚本.通过lua脚本的方式就可以实现上述条件的判断.

3. Redis事务实际操作

  1. 开启事务multi.
  2. 执行事务exec
  3. 放弃当前事务discard
shell 复制代码
127.0.0.1:6379[2]> keys *
(empty array)
127.0.0.1:6379[2]> MULTI
OK
127.0.0.1:6379[2]> set key1 value1
QUEUED
127.0.0.1:6379[2]> set key2 value2
QUEUED

我们看到,在事务中set key之后,会出现一个QUEUED,这就证明了该指令已经被放入了队列中,但是还没有被执行.如果我们另外打开一个客户端的话,发现这几个key并没有被设置.

shell 复制代码
127.0.0.1:6379> SELECT 2
OK
127.0.0.1:6379[2]> keys *
(empty array)

在我们执行了exec命令之后,才会执行事务.

shell 复制代码
127.0.0.1:6379[2]> EXEC
1) OK
2) OK
127.0.0.1:6379[2]> keys *
1) "key2"
2) "key1"

如果使用discard命令,事务就会被放弃.

shell 复制代码
127.0.0.1:6379[2]> MULTI
OK
127.0.0.1:6379[2]> set key3 value3
QUEUED
127.0.0.1:6379[2]> set key4 value4 
QUEUED
127.0.0.1:6379[2]> DISCARD
OK
127.0.0.1:6379[2]> keys *
1) "key2"
2) "key1"

注意: 要是我们在开启事务的过程中,没有提交事务,而是重新启动了Redis服务器,那么该事务就和discard的效果一样,直接被抛弃了.

但是有时候会出现以下的情况,比如在一个客户端1的事务中,我们设置了一个key,value值为222,但是在客户端1的事务还没有提交的时候,客户端2中设置了key的value值为333.此时客户端1对事务进行了提交.此时key的值就被重新设置为了222.

shell 复制代码
127.0.0.1:6379[2]> MULTI
OK
127.0.0.1:6379[2]> set key3 333
QUEUED
127.0.0.1:6379[2]> set key3 222
OK
127.0.0.1:6379[2]> EXEC
1) OK
127.0.0.1:6379[2]> get key3
"333"

这时候,我们就需要引入一个操作watch,这个watch的用途就是监控某个key是否在开启事务之后,执行事务之前发生了改变.想要在事务中监控某个key,避免在另一个客户端修改key,我们就可以在事务开启之前使用watch key操作.我们把刚才的操作引入watch再来操作一次.

shell 复制代码
127.0.0.1:6379[2]> del key3
(integer) 1
127.0.0.1:6379[2]> MULTI
OK
127.0.0.1:6379[2]> set key3 333
QUEUED
127.0.0.1:6379[2]> set key3 222
OK
127.0.0.1:6379[2]> EXEC
(nil)

我们看到,如果在事务提交之前,被watch的key如果在另一个客户端被修改之后,最后提交事务的时候,就会返回一个nil.证明这个事务执行是失败的.

那么watch的实现原理是怎样的呢?其实watch的实现原理就类似于我们在之前并发编程中学习的乐观锁的实现,即"解决CAS中的ABA问题"的策略.

和ABA问题中的实现策略一样,Redis的watch就相当于是基于版本号这样的机制,来实现了乐观锁.

还是那上面的例子.在watch key3之后,Redis就会记录key3的版本号,开启事务之后,如果在其他的客户端修改了key3的值,那么key3的版本号就会改变,在提交事务的时候,事务就会发现watch的key的版本号发生了改变,于是事务就会执行失败 .watch本质上就是给exec加上了一个对特定key的判定条件.

举例说明:判断老公是否出轨.

离开家之前,把枕头放到一个特定的位置,拍一张照片,如果回来之后,枕头的位置发生了改变,则证明老公出轨了.

相关推荐
卡布奇诺-海晨5 分钟前
MySQL的MVCC机制
数据库·mysql
hao_wujing31 分钟前
攻击模型的恶意行为检测
网络·数据库·php
秃头摸鱼侠1 小时前
MySQL查询语句(续)
数据库·mysql
MuYiLuck2 小时前
【redis实战篇】第八天
数据库·redis·缓存
睡觉待开机2 小时前
6. MySQL基本查询
数据库·mysql
�FENG2 小时前
Redis 安装配置和性能优化
redis·持久化
大熊猫侯佩2 小时前
由一个 SwiftData “诡异”运行时崩溃而引发的钩深索隐(三)
数据库·swiftui·swift
大熊猫侯佩2 小时前
由一个 SwiftData “诡异”运行时崩溃而引发的钩深索隐(二)
数据库·swiftui·swift
大熊猫侯佩2 小时前
用异步序列优雅的监听 SwiftData 2.0 中历史追踪记录(History Trace)的变化
数据库·swiftui·swift
大熊猫侯佩2 小时前
由一个 SwiftData “诡异”运行时崩溃而引发的钩深索隐(一)
数据库·swiftui·swift