【Redis】深入解析 Redis 事务:特性、操作及其与 MySQL 事务的区别

ops/2024/11/9 16:34:05/

目录

    • Redis 事务
      • 什么是事务
      • 事务操作
        • WATCH 的实现原理


Redis 事务

什么是事务

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

回顾 MySQL 事务:

  • 原子性:把多个操作打包成一个整体了
  • 一致性:事务执行之前和之后,数据都不能离谱
  • 持久性:事务中做出的修改都会存硬盘
  • 隔离性:事务并发执行涉及到的一些问题

但是注意体会 Redis 的事务和 MySQL 事务的区别:

  • 弱化的原⼦性:redis 没有 “回滚机制”,只能做到这些操作 “批量执⾏”,不能做到 “⼀个失败就恢复到初始状态”,也就是不保证全部执行成功,其中有失败的就失败了,不理会。
  • 不保证⼀致性:不涉及 “约束”,也没有回滚。MySQL 的⼀致性体现的是运⾏事务前和运⾏后,结果都是合理有效的,不会出现中间⾮法状态。
  • 不需要隔离性:也没有隔离级别,因为不会并发执⾏事务(redis 单线程处理请求)。
  • 不需要持久性:是保存在内存的。是否开启持久化,是 redis-server ⾃⼰的事情,和事务⽆关。

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

就官方角度而言,也不太认为他们自己是具有原子性的,可能更加认同原子性更贴合 MySQL 的那种设定,也就是 MySQL 这种才算事务。

Redis 事务本质上是在服务器上搞了⼀个 “事务队列”。每次客⼾端在事务中进⾏⼀个操作,都会把命令先发给服务器,放到 “事务队列” 中,这是保证不被插队(但是并不会⽴即执⾏)

⽽是会在真正收到 EXEC 命令之后,才真正执⾏队列中的所有操作。此时 Redis 主线程就会把事务的所有操作都执行完,再处理其他客户端的操作

问:Redis 的事务为什么不设计得和 MySQL 一样强大呢?

答:MySQL 的事务是付出了很大的代价。空间上要花费更多的空间来存储更多的数据;时间上也要有更大的执行开销。正是因为这些问题,Redis 才有上场的机会,Redis 实现了 MySQL 的事务的话就失去了本身的优势。

Redis 事务的应用场景:相比 MySQL 的事务其实应用场景没那么多。Redis 如果是按照集群模式部署,是不支持事务的。

事务操作

MULTI

multi

开启⼀个事务. 执⾏成功返回 OK.

实例

127.0.0.1:6379> MULTI
OK

EXEC

exec

真正执⾏事务.

实例

127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> set k1 1
QUEUED
127.0.0.1:6379> set k2 2
QUEUED
127.0.0.1:6379> set k3 3
QUEUED
127.0.0.1:6379> EXEC
1) OK
2) OK
3) OK

每次添加⼀个操作,都会提⽰ “QUEUED”,说明命令已经进⼊客⼾端的队列了。

真正执⾏ EXEC 的时候,客⼾端才会真正把上述操作发送给服务器.

此时就可以获取到上述 key 的值了.

127.0.0.1:6379> get k1
"1"
127.0.0.1:6379> get k2
"2"
127.0.0.1:6379> get k3
"3"

DISCARD

discard

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

实例

127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> set k1 1
QUEUED
127.0.0.1:6379> set k2 2
QUEUED
127.0.0.1:6379> DISCARD
OK127.0.0.1:6379> get k1
(nil)
127.0.0.1:6379> get k2
(nil)

当开启事务,并且给服务器发送若干命令之后,此时服务器重启,效果相当于 discard

WATCH

watch

在执⾏事务的时候,如果某个事务中修改的值,被别的客⼾端修改了,此时就容易出现数据不⼀致的问题。watch 必须搭配事务使用,并且是在 multi 之前使用

实例

# 客⼾端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
1) OK

此时,key 的值是多少呢??

从输⼊命令的时间看,是客⼾端1 先执⾏的 set key 100。客⼾端2 后执⾏的 set key 200

但是从实际的执⾏时间看,是客⼾端2 先执⾏的,客⼾端1 后执⾏的

127.0.0.1:6379> get key 
"100"

这个时候,其实就容易引起歧义.

因此,即使不保证严格的隔离性,⾄少也要告诉⽤⼾,当前的操作可能存在⻛险.

watch 命令就是⽤来解决这个问题的。watch 在该客⼾端上监控⼀组具体的 key

当开启事务的时候,如果对 watch 的 key 进⾏修改,就会记录当前 key 的 “版本号”. (版本号是个简单的整数,每次修改都会使版本变⼤。服务器来维护每个 key 的版本号情况)

在真正提交事务的时候,如果发现当前服务器上的 key 的版本号已经超过了事务开始时的版本号,就会让事务执⾏失败。(事务中的所有操作都不执⾏)

实例

客⼾端1 先执⾏

127.0.0.1:6379> watch k1 # 开始监控 k1
OK
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> set k1 100 # 进⾏修改,从服务器获取 k1 的版本号是 0. 记录 k1 的版本
QUEUED
127.0.0.1:6379> set k2 1000 
QUEUED

只是⼊队列,但是不提交事务执⾏

客⼾端2 再执⾏

127.0.0.1:6379> set k1 200 # 修改成功,使服务器端的 k1 的版本号 0 -> 1
OK

客⼾端1 再执⾏

127.0.0.1:6379> EXEC # 真正执⾏修改操作,此时对⽐版本发现,客⼾端的 k1 的版本
(nil)127.0.0.1:6379> get k1
"200"127.0.0.1:6379> get k2
(nil)

此时说明事务已经被取消了. 这次提交的所有命令都没有执⾏

UNWATCH

unwatch

取消对 key 的监控.

相当于 WATCH 的逆操作. 此处不做演⽰。

WATCH 的实现原理

watch 的实现,类似于一个“乐观锁”

乐观锁,悲观锁不是指一个具体的锁,而是指某一类锁的特性

  • 乐观锁:加锁之前,预期后续锁冲突的概率比较低(乐观锁,防君子不防小人)
  • 悲观锁:加锁之前,预期后续锁冲突的概率比较高

锁冲突:两个线程针对同一个锁枷锁,一个能加锁成功,另一个就只能阻塞等待

对于锁冲突概率高或低,接下来要做的工作是不一样的,两者应对场景不一样

Java 的 synchronized 可以在悲观/乐观之间自适应,实际上是一种悲观锁的实现,因为它总是假设有并发冲突发生。当一个线程尝试获取锁而锁被占用时,该线程并不会立即进入阻塞状态。相反,它会进行几次“自旋”,在某种程度上接近乐观锁的行为(尝试不阻塞线程)

Redis 的 watch 相当于是基于 版本号,来实现了“乐观锁”

当执行 watch key 时就会给这个 key 安排一个版本号,这个版本号可以理解成一个“整数”,每次修改时,版本号都会“变大”。在执行事务中命令的时候就会判定这个 key 的版本号和最初 watch 的时候记录的版本号是否一致。如果一致说明没有被别的客户端修改,才会真正执行事务的操作;如果不一致说明被别的客户端修改,就会直接丢弃事务的操作,然后返回一个 nil。

这样的设定类似 CAS 的 ABA 问题

Redis 的 lua脚本,也能起到类似于事务的效果。官网上说,事务这里的任何能实现的效果,都可以用 lua脚本代替。


http://www.ppmy.cn/ops/106237.html

相关文章

【Arm Cortex-X925】 -【第四章】-时钟和复位

4. 时钟和复位 为了提供动态功耗节省,Cortex-X925 核心支持层次时钟门控。它还支持温复位和冷复位。 每个 Cortex-X925 核心有一个单一的时钟域,并接收一个单一的时钟输入。这个时钟输入由 CPU 桥中的架构时钟门控控制。 此外,Cortex-X925…

紫光展锐完成Android 15同步升级,驱动技术创新与生态共赢

近日,紫光展锐宣布,展锐5G移动平台T820、T770、T765、T760、T750以及4G平台T620、T619、T616、T615、T612、T606,完成Android 15同步升级。相较于过往Android发布,今年同步升级Android 15主要有三大提升: ■ 紫光展锐实…

模型从 HuggingFace 转存到 ModelScope

由于 HuggingFace 网络访问比较慢,国内通常会使用魔搭下载模型,如果魔搭上还没有,需要从 HuggingFace 准存一下,本文将通过 Colab AliyunPan 的方式下载模型并进行转存。 登录Colab 并运行一下命令 安装依赖包,Hugg…

Pytorch中向量和张量

在 PyTorch 中,向量和张量是重要的概念,它们用于表示和操作数据。下面是对这两个概念的解释: 向量 定义:向量是一个一维数组,它包含一系列有序排列的数字。在数学上,向量可以用于表示坐标、速度等物理量。…

【QT】学习笔记:处理数据库 SQLite

在 Qt 中使用 SQLite 数据库非常简单,Qt 提供了 QSqlDatabase 和 QSqlQuery 类来处理数据库的连接、查询、插入、更新和删除等操作。下面是一个示例程序,展示如何在 Qt 中使用 SQLite 数据库。 示例代码 1. 项目配置 首先,确保在项目的 .p…

【2024高教社杯全国大学生数学建模竞赛】B题 生产过程中的决策问题——解题思路 代码 论文

目录 问题 1:抽样检测方案的设计问题 2:生产过程中的决策问题 3:多工序、多零配件的生产决策问题 4:重新分析次品率题目难度分析1. 统计检测方案设计的复杂性(问题 1)2. 多阶段生产决策的复杂性&#xff08…

第 21 章 DOM 操作表格及样式

第 21 章 DOM 操作表格及样式 1.操作表格 2.操作样式 DOM 在操作生成 HTML 上,还是比较简明的。不过,由于浏览器总是存在兼容和陷阱,导致最终的操作就不是那么简单方便了。本章主要了解一下 DOM 操作表格和样式的一些知识。 一&#xff0…

【ArcGIS/GeoScenePro】Portal和Server关系

简介 以下是ArcGIS的整体架构图 上图简化后 从图中我们可以看出可以将其分为三层其中: 最上层:应用层 中间层(门户):连接应用层和服务器,对server上发布的服务进行管理、分享和权限分配 最低层:服务器(Server层) 其中Enterprise = portal(中间层)+server(最底…