[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的判定条件.

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

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

相关推荐
远歌已逝2 小时前
维护在线重做日志(二)
数据库·oracle
只因在人海中多看了你一眼2 小时前
分布式缓存 + 数据存储 + 消息队列知识体系
分布式·缓存
qq_433099403 小时前
Ubuntu20.04从零安装IsaacSim/IsaacLab
数据库
Dlwyz3 小时前
redis-击穿、穿透、雪崩
数据库·redis·缓存
工业甲酰苯胺5 小时前
Redis性能优化的18招
数据库·redis·性能优化
没书读了6 小时前
ssm框架-spring-spring声明式事务
java·数据库·spring
i道i6 小时前
MySQL win安装 和 pymysql使用示例
数据库·mysql
小怪兽ysl6 小时前
【PostgreSQL使用pg_filedump工具解析数据文件以恢复数据】
数据库·postgresql
wqq_9922502777 小时前
springboot基于微信小程序的食堂预约点餐系统
数据库·微信小程序·小程序
爱上口袋的天空7 小时前
09 - Clickhouse的SQL操作
数据库·sql·clickhouse