当前位置: 首页 > news >正文

【Redis 进阶】事务

Redis 的事务和 MySQL 的事务概念上是类似的,都是把一系列操作绑定成一组,让这一组能够批量执行。


一、Redis 的事务和 MySQL 事务的区别

1、MySQL 事务

  • 原子性:把多个操作打包成一个整体。(要么全都做,要么都不做)
  • ⼀致性:事务执行前和执行后,数据得保持相同
  • 隔离性:事务并发执行涉及到的一些问题(脏读、幻读等)。
  • 持久性:事务中做出的修改都会存储到硬盘中。

2、Redis 的事务

  • 弱化的原子性:redis 没有 “回滚机制”,只能做到这些操作 “批量执行”,不能做到 “一个失败就恢复到初始状态”,也就是无法保证执行成功。(网上有的说 Redis 事务有原子性(只是打包一起执行),有的说没有原子性(打包一起执行 + 带有回滚 —— 打包一起正确执行))
  • 不保证⼀致性:不涉及 “约束”,也没有回滚(MySQL 的一致性体现的是运行事务前和运行后,结果都是合理有效的,不会出现中间非法状态)。事务在执行过程中如果某个修改操作出现失败,就可能引起不一致的情况。
  • 不需要隔离性:也没有隔离级别,因为不会并发执行事务(Redis 是一个单线程模型的服务器程序,所有的请求 / 事务都是 “串行” 执行的)。
  • 不需要持久性:Redis 本身就是内存数据库,数据是存储在内存中的。虽然 Redis 也有持久化机制,但是否开启持久化是 redis-server 自己的事情,和事务无关。

Redis 事务本质上是在服务器上搞了⼀个 “事务队列”,每次客户端在事务中进行一个操作,都会把命令先发给服务器,放到 “事务队列” 中,但并不会立即执行,而是在收到 EXEC 命令后,才按照顺序依次执行队列中的所有操作(在 Redis 主线程中完成的,主线程会把事务中的操作都执行完,再处理别的客户端)。

因此,Redis 的事务的功能相比于 MySQL 来说,是弱化很多的。只能保证事务中的这几个操作是 “连续的”,不会被别的客户端 “加塞”,仅此而已。


为什么 Redis 不设计成和 MySQL 一样强大呢?

MySQL 的事务付出了很大的代价:

  • 在空间上,需要花费更多的空间来存储更多的数据。
  • 在时间上,也要有更大的执行开销。

正是因为 Redis 简单、高效的特点,才能够在分布式系统中弥补一些 MySQL 不擅长的场景。


什么时候需要使用到 Redis 事务呢?

如果我们需要把多个操作打包进行,使用事务是比较合适的。之前在多线程中是通过加锁的方式来避免 “插队” 的,而在 Redis 中直接使用事务即可。

在上面这个场景没有加锁也能解决问题。

Redis 命令里能够进行类似上图中的条件判断吗?

Redis 原生命令中确实没有这种条件判断,但是 Redis 支持 lua 脚本。通过 lua 脚本就可以实现上述的条件判定,并且也和事务一样是打包批量执行的。

lua 脚本的实现方式是 Redis 事务的进阶版本,此处对 lua 脚本不做过多的讨论。

注意:如果 Redis 是按照集群模式部署的话,是不支持事务的。 


二、事务操作

1、MULTI

开启一个事务,执行成功返回 OK。


2、EXEC

真正执行事务。

每次添加一个操作,都会提示 "QUEUED",说明命令已经进入客户端的事务队列中。此时如果另外开一个客户端,再尝试查询这几个 key 对应的数据,是没有结果的:

只有当真正执行 EXEC 的时候,客户端才会真正把上述操作发送给服务器,此时就可以获取到上述 key 的值了。

此时,另一个客户端再次查询结果也是如此。


3、DISCARD

放弃当前事务,此时直接清空事务队列,之前的操作都不会真正执行到。

当开启事务并给服务器发送若干个命令之后,服务器重启,那么此时这个事务怎么办呢?

此时的效果就等同于 discard。


4、WATCH

在执行事务的时候,如果某个事务中修改的值被别的客户端修改了,此时就容易出现数据不一致的问题。

客户端 1 先执行:

客户端 2 再执行:

客户端 1 最后执行:

此时 key 的值是多少呢?

从输入命令的时间看,是客户端 1 先执行的 set key 222,客户端 2 后执行的 set key 333。但是从实际的执行时间来看,是客户端 2 先执行的,客户端 1 后执行的。

由于客户端 1 得是 exec 执行了,才会真正执行 set key 222,所以这个操作实际上更晚执行,最终值就是 222.

这个时候其实就容易引起歧义。因此,即使不保证严格的隔离性,至少也要告诉用户,当前的操作可能存在风险。watch 命令就是用来解决上述这个问题的,watch 在该客户端上监控一组具体的 key,看看这个 key 在事务的 multi 和 exec 之间,set key 之后,是否在外部被其他客户端修改了。

  • 当开启事务的时候,如果对 watch 的 key 进行修改,就会记录当前 key 的 “版本号”(版本号可以理解成一个整数,每次修改都会使版本变大,服务器来维护每个 key 的版本号情况)
  • 在真正提交事务的时候,如果发现当前服务器上的 key 的版本号已经超过了事务开始时的版本号,就会让事务执行失败(事务中的所有操作都不执行)。

客户端 1 先执行:

watch 本质上是给 exec 加一个判定条件。

key 进行修改从服务器获取 key 的版本号是 0,记录 key 的版本号(还没真的修改,版本号不变)

这里只是入队列,但是不提交事务执行。

客户端 2 再执行:

修改成功,使服务器端的 key 的版本号 0 -> 1

客户端 1 最后执行:

exec 在执行上述事务中的命令时,此处就会做出判定。对比版本发现客户端的 key 的版本号是 0,服务器上的版本号是 1,版本不一致,说明有其他客户端在事务中间修改了 key,说明事务被取消了,于是真正执行 set key 222 的时候就没有真正执行。

客户端 2 执行:


(1)watch 的实现原理

watch 的实现类似于一个 “乐观锁”(不是指某个具体的锁,而指的是某一类锁的特性)。

  • 乐观锁(成本低):加锁之前就有一个心理预期,预期接下来锁冲突的概率比较低。
  • 悲观锁(成本高):加锁之前就有一个心理预期,预期接下来锁冲突(两个线程针对同一个锁加锁,一个能加锁成功,另一个就得阻塞等待)的概率比较高。

锁冲突概率高和冲突概率低,意味着接下来要做的工作是不一样的。

C++ Linux 中涉及到的锁 mutex / std::mutex 都是悲观锁,Java synchronized 则是可以在悲观和乐观之间自适应。


5、UNWATCH

取消对 key 的监控,相当于 WATCH 的逆操作。

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 51单片机和STM32区别
  • vite+typescript项目 报错:找不到模块“./*.vue”或其相应的类型声明——解决方案
  • 仓颉语言 -- 网络编程
  • 编程入门:大学新生的指南与策略
  • Docker 和 Docker Compose 的区别对比
  • AI学习指南机器学习篇-Q学习的优缺点
  • Python面试宝典第25题:括号生成
  • 反序列化靶机serial
  • ThreadLocal:线程本地变量的作用与应用
  • 8G内存的Mac够用吗 ?苹果电脑内存满了怎么清理?可以有效地管理和优化你的Mac电脑内存,确保设备运行流畅
  • 开源跨平台SQL编辑器:Beekeeper Studio
  • Python中的异常处理除了Try语句,你还会啥?
  • 安装jdk和tomcat
  • KVM+GFS分布式文件系统构建KVM高可用
  • Vue3+TypeScript+printjs 实现标签批量打印功能
  • 【刷算法】从上往下打印二叉树
  • 230. Kth Smallest Element in a BST
  • 30天自制操作系统-2
  • Android Studio:GIT提交项目到远程仓库
  • Angular 2 DI - IoC DI - 1
  • javascript 总结(常用工具类的封装)
  • Transformer-XL: Unleashing the Potential of Attention Models
  • webpack+react项目初体验——记录我的webpack环境配置
  • 不用申请服务号就可以开发微信支付/支付宝/QQ钱包支付!附:直接可用的代码+demo...
  • 对话:中国为什么有前途/ 写给中国的经济学
  • 翻译 | 老司机带你秒懂内存管理 - 第一部(共三部)
  • 规范化安全开发 KOA 手脚架
  • 聊聊flink的BlobWriter
  • 你不可错过的前端面试题(一)
  • 让你成为前端,后端或全栈开发程序员的进阶指南,一门学到老的技术
  • 日剧·日综资源集合(建议收藏)
  • 三栏布局总结
  • 使用API自动生成工具优化前端工作流
  • 小程序开发之路(一)
  • ​​​​​​​ubuntu16.04 fastreid训练过程
  • ​【经验分享】微机原理、指令判断、判断指令是否正确判断指令是否正确​
  • ​卜东波研究员:高观点下的少儿计算思维
  • ​第20课 在Android Native开发中加入新的C++类
  • ​软考-高级-系统架构设计师教程(清华第2版)【第12章 信息系统架构设计理论与实践(P420~465)-思维导图】​
  • ​软考-高级-信息系统项目管理师教程 第四版【第14章-项目沟通管理-思维导图】​
  • #### go map 底层结构 ####
  • $().each和$.each的区别
  • $.ajax()
  • (20)目标检测算法之YOLOv5计算预选框、详解anchor计算
  • (27)4.8 习题课
  • (react踩过的坑)Antd Select(设置了labelInValue)在FormItem中initialValue的问题
  • (带教程)商业版SEO关键词按天计费系统:关键词排名优化、代理服务、手机自适应及搭建教程
  • (实战篇)如何缓存数据
  • (顺序)容器的好伴侣 --- 容器适配器
  • (一)、python程序--模拟电脑鼠走迷宫
  • (原創) 是否该学PetShop将Model和BLL分开? (.NET) (N-Tier) (PetShop) (OO)
  • **PyTorch月学习计划 - 第一周;第6-7天: 自动梯度(Autograd)**
  • .bat批处理(五):遍历指定目录下资源文件并更新
  • .dat文件写入byte类型数组_用Python从Abaqus导出txt、dat数据
  • .NET Core 中的路径问题