【精通Redis】Redis事务

文章目录

  • 前言
  • 一、标准事务
    • 1.1 标准事务的特性
    • 1.2 标准事务的生命周期
    • 1.3 事务的作用
  • 二、Redis事务
    • 2.1 Redis事务的特性
    • 2.2 Redis事务与普通事务的区别
  • 三、Redis事务常用命令
  • 总结

前言

我们在使用Redis的时候,有时为了处理多个结构,需要向Redis中一次性发送多个命令。这一组命令我们要求不能被打断。Redis事务提供了一种将多个命令打包,然后一次性按顺序执行的能力。Redis事务和标准事务特性有所不同,Redis的事务不是标准事务,下面笔者逐步展开叙述。


一、标准事务

一说到事务,我们自然就会想到关系数据库中的事务,比如MySQL、Oracle、Sql Server等。事务(Transaction)在计算机科学中,特别是在数据库管理系统中,指的是一个逻辑上完整的工作单元,它包含了一系列的操作

1.1 标准事务的特性

标准事务具有以下几个核心特性,通常被称为 ACID 特性:

  1. 原子性(Atomicity):
    事务中的所有操作要么全部成功,要么全部失败。这意味着事务作为一个整体被执行,不能部分执行。
    如果事务的一部分失败,则整个事务都将被回滚到执行前的状态。

  2. 一致性(Consistency):
    事务的执行结果必须使数据库从一个一致性状态转换到另一个一致性状态。
    事务完成后,数据库必须处于有效的状态,满足所有的约束条件。

  3. 隔离性(Isolation):
    多个并发执行的事务之间是相互隔离的,一个事务的执行不应影响其他事务的执行。
    隔离级别可以有不同的设定,以平衡并发性和一致性之间的需求。

  4. 持久性(Durability):
    一旦事务提交,它对数据库所做的更改就是永久的,即使系统发生故障。
    已经提交的事务结果不会因任何系统故障而丢失。

1.2 标准事务的生命周期

标准事务通常由以下步骤构成:

  • 开始:通过 BEGIN TRANSACTION 或类似的命令开始事务。

  • 执行:执行一系列的数据库操作。

  • 提交:通过 COMMIT 命令提交事务,使其更改成为永久性的。

  • 回滚:通过 ROLLBACK 命令撤销事务,使数据库回到事务开始前的状态。

Java中的JDBC使用事务就是封装了上述四个操作,示例代码如下:

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;public class JDBCTransactionExample {public static void main(String[] args) {String url = "jdbc:mysql://localhost:3306/testdb";String user = "root";String password = "password";try (Connection conn = DriverManager.getConnection(url, user, password)) {// 关闭自动提交conn.setAutoCommit(false);// 准备 SQL 语句String sql1 = "INSERT INTO users (name, email) VALUES (?, ?)";String sql2 = "UPDATE accounts SET balance = balance - 100 WHERE user_id = ?";PreparedStatement pstmt1 = conn.prepareStatement(sql1);pstmt1.setString(1, "John Doe");pstmt1.setString(2, "john.doe@example.com");PreparedStatement pstmt2 = conn.prepareStatement(sql2);pstmt2.setInt(1, 1);// 执行 SQL 语句int rowsAffected1 = pstmt1.executeUpdate();int rowsAffected2 = pstmt2.executeUpdate();// 提交事务conn.commit();System.out.println("Transaction completed successfully.");} catch (SQLException e) {// 发生异常时回滚事务try {if (conn != null) {conn.rollback();}} catch (SQLException ex) {ex.printStackTrace();}e.printStackTrace();}}
}

代码中可以看到在成功提交事务之前发生的SQLException异常都会导致事务的回滚撤销,保证数据的一致性。

1.3 事务的作用

事务的作用主要体现在以下几个方面:

  1. 数据一致性
    事务确保数据的一致性,即事务执行前后数据必须处于一致的状态。通过保证事务的原子性、一致性、隔离性和持久性(ACID 特性),事务可以确保数据在并发操作下的正确性和一致性。

  2. 错误处理
    事务提供了错误处理机制。如果事务中的某个操作失败,可以通过回滚事务来撤销所有更改,从而使数据库恢复到事务开始前的状态。这有助于防止数据损坏和不一致的状态。

  3. 并发控制
    事务通过隔离性来控制并发操作,确保多个事务之间不会相互干扰。不同的隔离级别可以平衡并发性和数据一致性之间的需求。

  4. 简化复杂操作
    事务可以将一系列相关操作封装在一起,作为一个整体来执行。这有助于简化应用程序中的复杂业务逻辑,例如转账操作、库存更新等。

  5. 安全性
    事务提供了一种安全的方式来执行数据库操作,确保数据的完整性和准确性。通过事务管理,可以减少因程序错误导致的数据丢失或损坏的风险。

  6. 性能优化
    在某些情况下,事务还可以帮助优化性能,例如通过减少锁定时间或优化查询计划。
    例如,在一些数据库系统中,事务可以减少锁定资源的时间,从而提高并发性能。

  7. 简化编程模型
    事务提供了一种简单的方式来处理复杂的业务逻辑,使得程序员可以更容易地编写和维护代码。

通过使用事务,可以避免编写复杂的错误处理和数据同步代码。比如在一次操作中修改了很多个表的数据,这时发生了异常,那么我们需要对修改了数据的表进行恢复还原,如果没有事务,我们需要手动编写大量的补偿代码,把数据还原掉。


二、Redis事务

Redis事务是一种机制,它允许客户端将一组命令作为一个整体发送到Redis服务器。这些命令会被服务器接收并按顺序执行。Redis事务的主要目的是确保这一组命令能够作为一个逻辑单元被执行,而不是单独的命令。

2.1 Redis事务的特性

Redis事务主要有如下特性:

  1. 命令排队:在事务开始后(通过MULTI命令),所有的命令都会被放入一个事务队列中等待执行。

  2. 原子性:尽管Redis事务保证了命令的执行顺序,但是它并不保证原子性。这意味着如果其中一个命令失败,其他的命令仍然会被执行(注意Redis的单个命令是原子性的)。

  3. 事务回滚:Redis事务不支持回滚,一旦事务开始执行,即使某些命令失败也不会取消整个事务。

  4. 监视器和条件执行:Redis提供了WATCH命令来监视一个或多个键,如果在执行EXEC之前这些键被其他客户端修改,则整个事务不会被执行。

  5. 响应延迟:事务中的所有命令都是在一个网络往返内发送的,这可能会减少网络延迟。

2.2 Redis事务与普通事务的区别

  1. 原子性:传统的关系型数据库事务具有ACID特性,即原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability)。而 Redis事务不提供原子性 ,除了WATCH机制提供的有限形式的一致性保障外,事务中的命令要么全部执行,要么部分执行。

  2. 回滚机制:关系型数据库通常支持回滚,即如果事务的一部分失败,可以通过回滚撤销所有已执行的操作。Redis事务没有回滚机制

  3. 隔离级别:关系型数据库事务支持不同的隔离级别,如读未提交、读已提交、可重复读、串行化等,以防止脏读、不可重复读等问题。Redis事务不提供这样的隔离级别。

  4. 持久性:关系型数据库事务提交后,数据会被持久化到磁盘上。Redis事务提交后,数据也会被持久化,但这取决于Redis的持久化配置。

总的来说,Redis事务更侧重于命令的有序执行,而不是提供强一致性和事务回滚功能。这使得 Redis事务在性能上通常优于传统的关系型数据库事务,但也意味着它在某些场景下可能不适合用于需要严格事务一致性的应用


三、Redis事务常用命令

Redis 的事务机制提供了一种方式来确保一组命令作为一个整体被执行。虽然 Redis 本身是一个单线程模型,并且每个命令都是原子性的,但是事务可以让你更好地控制命令的执行顺序和一些并发场景下的数据一致性问题。
以下是 Redis 事务相关的几个重要命令:

  • MULTI
    开始一个新的事务块。
    之后的所有命令都会被放入队列中,直到调用 EXEC 命令。

  • EXEC
    提交事务,执行所有在 MULTI 命令后排队的命令。
    如果事务中有任何命令执行失败,整个事务仍然会被执行,但失败的命令会返回错误。

  • DISCARD
    取消事务,放弃执行事务块中的所有命令。
    这个命令可以用来取消从 MULTI 开始以来的所有排队命令。

  • WATCH
    监视一个或多个键。
    如果在发出 WATCH 命令之后,但 EXEC 命令之前,有其他客户端改变了任何一个被监视的键,那么整个事务将被取消。

  • UNWATCH
    取消 WATCH 命令对所有键的监视。
    这个命令可以在事务之外使用,以取消之前的 WATCH 命令的效果。

我们日常使用Redis命令很少使用原生命令,一般都是通过封装的依赖库,去发送命令执行,这里笔者使用Jedis去演示这些Redis事务相关命令。

package com.hl.redisdemo;import redis.clients.jedis.Jedis;
import redis.clients.jedis.Transaction;import java.util.List;public class RedisTransactionCommandsDemo {public static void main(String[] args) {// 创建 Jedis 实例try (Jedis jedis = new Jedis("127.0.0.1", 6379)) {// 清除之前的键值jedis.del("key1", "key2");// 使用 WATCH 命令监视 key1jedis.watch("key1");// 检查 key1 的当前值String currentValue = jedis.get("key1");if (currentValue != null) {System.out.println("当前 key1 的值: " + currentValue);} else {System.out.println("key1 不存在");}// 开始事务Transaction transaction = jedis.multi();// 添加命令到事务队列transaction.set("key1", "新值");transaction.get("key1");transaction.set("key2", "值2");// 在这里可以模拟其他客户端修改 key1// 注意:这需要另一个 Redis 客户端来修改 key1//jedis.set("key1", "被其他客户端修改"); // 仅用于演示,实际运行时应注释掉// 执行事务List<Object> result = transaction.exec();if (result != null) {System.out.println("事务执行成功。");System.out.println("设置 key1 的结果: " + result.get(0));System.out.println("获取 key1 的结果: " + result.get(1));System.out.println("设置 key2 的结果: " + result.get(2));} else {System.out.println("由于 WATCH 失败,事务未执行。");}// 取消监视jedis.unwatch();// 开始新的事务并取消transaction = jedis.multi();transaction.set("key1", "已取消的值");transaction.get("key1");transaction.discard();System.out.println("事务已取消。");// 开始新的事务并执行transaction = jedis.multi();transaction.set("key1", "最终值");transaction.get("key1");result = transaction.exec();if (result != null) {System.out.println("最终事务执行成功。");System.out.println("设置 key1 的结果: " + result.get(0));System.out.println("获取 key1 的结果: " + result.get(1));} else {System.out.println("最终事务失败。");}} catch (Exception e) {System.err.println("执行事务时发生错误: " + e.getMessage());}}
}

以上代码执行结果如下:

在这里插入图片描述

注意代码中有一行:

jedis.set("key1", "被其他客户端修改"); 

这行代码是被注释的,如果放开,就表示Redis事务在执行过程中,键被修改,会发生如下报错:
在这里插入图片描述

watch和unwatch这两个关键字一般是配合MULTI和EXEC命令用的,在事务开启之前使用watch监听键,在事务结束之后使用unwatch释放监听。下面写了两个例子:

package com.hl.redisdemo;import redis.clients.jedis.Jedis;
import redis.clients.jedis.Transaction;import java.util.List;public class RedisTransactionCommandsDemo {public static void main(String[] args) {// 创建 Jedis 实例try (Jedis jedis = new Jedis("127.0.0.1", 6379)) {// 清除之前的键值jedis.del("key1", "key2");// 使用 WATCH 命令监视 key1jedis.watch("key1");// 检查 key1 的当前值String currentValue = jedis.get("key1");if (currentValue != null) {System.out.println("当前 key1 的值: " + currentValue);} else {System.out.println("key1 不存在");}// 开始事务Transaction transaction = jedis.multi();Thread.sleep(10000); // 等待其他客户端修改key1// 添加命令到事务队列transaction.set("key1", "新值");transaction.get("key1");transaction.set("key2", "值2");// 执行事务List<Object> result = transaction.exec();if (result != null) {System.out.println("事务执行成功。");System.out.println("设置 key1 的结果: " + result.get(0));System.out.println("获取 key1 的结果: " + result.get(1));System.out.println("设置 key2 的结果: " + result.get(2));} else {System.out.println("由于 WATCH 失败,事务未执行。");}// 取消监视jedis.unwatch();} catch (Exception e) {System.err.println("执行事务时发生错误: " + e.getMessage());}}
}

这段代码在Redis事务开启后线程睡10秒,等待其他Redis客户端修改键key1,另一个客户端代码如下:

package com.hl.redisdemo;import redis.clients.jedis.Jedis;public class OtherClientDemo {public static void main(String[] args) throws InterruptedException {// 创建 Jedis 实例try (Jedis jedis = new Jedis("127.0.0.1", 6379)) {// 模拟其他客户端修改 key1jedis.set("key1", "被其他客户端修改");System.out.println("其他客户端修改了 key1 的值为: " + jedis.get("key1"));} catch (Exception e) {System.err.println("模拟客户端发生错误: " + e.getMessage());}}
}

先执行RedisTransactionCommandsDemo 中的main方法开启Redis事务,再立刻执行OtherClientDemo中的main方法,观察到 RedisTransactionCommandsDemo的main方法控制台打印结果如下:

在这里插入图片描述
说明一个Redis客户端监听一个键后,开启事务,如果有其他Redis客户端在该事务开启后结束前去尝试修改这个键对应的值,那么会导致该事务取消。注意unwatch必须在事务EXEC之后使用,且必须使用,否则会造成资源占用,键会一直被监听,影响其他客户端操作。


总结

本篇文章以标准事务为引导,叙述了其特性,接着讨论了Redis事务和普通关系数据库事务的区别。介绍了Redis事务相关的五个命令,并使用Java代码展示了它们的用法,通过本篇文章的学习,我们应该能够理解掌握Redis事务相关命令的使用场景和注意事项。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/390434.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Linux系统窗口水印难点分析

给应用程序加水印是保护数据的一种方式&#xff0c;window上可以通过给进程通过注入的方法给进程的窗口创建一个同大小的副窗口&#xff0c;在副窗口上绘制水印内容&#xff0c;同时设置副窗口透明同时透传事件&#xff0c;这样就可以达到在源窗口上显示水印的效果且不影响程序…

深⼊理解指针(3)

1. 字符指针变量 2. 数组指针变量 3. ⼆维数组传参的本质 4. 函数指针变量 5. 函数指针数组 6. 转移表 1. 字符指针变量 在指针的类型中我们知道有⼀种指针类型为字符指针 ⼀般使⽤: char* 这两种方式都是把字符串中的首字符的地址赋值给pc。 在这串代码中 str1内容的地…

ArkTS通用属性

目录 一、尺寸设置 宽高&#xff0c;外边距&#xff0c;内边距&#xff0c;尺寸size layoutWeight constraintSize 二、位置设置 align direction position offset 使用Edge方式position,offset 三、布局约束 aspectRatio displayPriority 四、Flex布局 flexBas…

RabbitMQ高级篇(如何保证消息的可靠性、如何确保业务的幂等性、延迟消息的概念、延迟消息的应用)

文章目录 1. 消息丢失的情况2. 生产者的可靠性2.1 生产者重连2.2 生产者确认2.3 生产者确认机制的代码实现2.4 如何看待和处理生产者的确认信息 3. 消息代理&#xff08;RabbitMQ&#xff09;的可靠性3.1 数据持久化3.2 LazyQueue&#xff08; 3.12 版本后所有队列都是 Lazy Qu…

如何对我们要多次使用的页面进行一个抽取

有的时候,一个页面我们要多次使用,该怎么抽取呢? 创建一个文件夹,用于存放多次使用的页面 将要多次使用的组件(<template>)和风格(<style>)剪切出来,放入新建的页面 直接进行引用 导入 然后就可以使用

嵌入式C++、QML与MQTT:智能化农业灌溉管理系统设计思路(代码示例)

目录 一、项目概述 二、系统架构 三、环境搭建 1. 硬件环境 2. 软件环境 四、代码实现 1. 硬件端代码示例 2. 软件端代码示例 a. 后端代码&#xff08;Node.js MQTT&#xff09; b. 前端代码&#xff08;QML&#xff09; 五、项目总结 一、项目概述 随着全球对农业…

文件包含漏洞Tomato靶机渗透_详解

一、导入靶机 将下载好的靶机拖入到VMware中&#xff0c;填写靶机机名称(随便起一个)和路径 虚拟机设置里修改网络状态为NAT模式 二、信息收集 1、主机发现 用御剑扫描工具扫描虚拟机的NAT网段&#xff0c;发现靶机的IP是192.168.204.141 2、端口扫描 用御剑端口扫描扫描全…

windows 文件夹下的文件名称全部输入到txt文件中(已解决)

打开cmd 命令行&#xff0c;记住一定是cmd命令行 进入cmd 目前在C盘&#xff0c;跳转D盘&#xff0c;输入d:。 d: 回车&#xff1b; 在输入或者粘贴你的目的路径 我的是 D:\opencv****\build\x64\vc14\lib&#xff0c;回车进入目的路径。 然后 再输入&#xff1a;dir /b &…

Tantivy使用Rust 开发的全文搜索引擎库

一、概述 Tantivy是一个全文搜索引擎库&#xff0c;灵感来自Apache Lucene&#xff0c;用Rust编写。 如果你正在寻找Elasticsearch或Apache Solr的替代品&#xff0c;请查看我们基于Tantivy构建的分布式搜索引擎Quiuckwit。 Tantivy更接近Apache Lucene&#xff0c;而不是E…

K8s集群部署

操作系统初始化配置 #关闭防火墙 systemctl stop firewalld systemctl disable firewalld iptables -F && iptables -t nat -F && iptables -t mangle -F && iptables -X#关闭selinux setenforce 0 sed -i s/enforcing/disabled/ /etc/selinux/config…

当Vercel的域名验证规则碰上JPDirect这种不配合的同学把我的脑袋擦出了火星子

文章目录 前言问题简单说明Vercel主要功能和特点 JPDirectNameServers解决方案 总结 前言 处理域名转移这件事已经过去好几天&#xff0c;终于抽出点时间来总结一下&#xff0c;解决这件事大概花了2周多时间&#xff0c;因为时差的原因导致沟通缓慢&#xff0c;今天准备长话短…

Python 爬虫项目实战(二):爬取微博热搜榜

前言 网络爬虫&#xff08;Web Crawler&#xff09;&#xff0c;也称为网页蜘蛛&#xff08;Web Spider&#xff09;或网页机器人&#xff08;Web Bot&#xff09;&#xff0c;是一种按照既定规则自动浏览网络并提取信息的程序。爬虫的主要用途包括数据采集、网络索引、内容抓…

L-H、BytePlus 和 INOVAI在东京成功举办Web3 AI未来峰会

7月30日&#xff0c;L-H (Legendary Humanity)、字节跳动旗下BytePlus 和日本知名Web3孵化器 INOVAI 在东京联合举办Web3&AI未来峰会&#xff0c;水滴资本等行业重磅机构共同参与此次峰会&#xff0c;探讨AI与 Web3的融合性未来。 在此次峰会上&#xff0c;L-H (Legendary…

分布式领域扩展点设计稿

分布式领域扩展点设计稿 背景坐标设计理念设计图Quick Start相关组件 背景 随着交易业务和基础知识的沉淀&#xff0c;愈发觉得扩展点可以在大型交易分布式架构中可以做更多的事情。 经过一个月的思考&#xff0c;决定将 单点领域扩展点&#xff08;savior-ext&#xff09; 从…

特定领域软件架构-系统架构师(三十七)

软件架构复用 有三个阶段&#xff1a; 首先构造/获取可复用的软件资产其次管理这些资产&#xff08;构件库&#xff09;最后针对这些需求&#xff0c;从这些资产中选择可复用的部分&#xff0c;满足需求应用系统。 特定领域软件架构 DSSA&#xff08;Domain Specific softwa…

【C++】入门基础知识

河流之所以能够到达目的地&#xff0c;是因为它懂得怎样避开障碍。&#x1f493;&#x1f493;&#x1f493; ✨说在前面 亲爱的读者们大家好&#xff01;&#x1f496;&#x1f496;&#x1f496;&#xff0c;我们又见面了&#xff0c;上一篇目我们已经完结了初阶数据结构部分…

php反序列化靶机serial实战

扫描ip,找到靶机ip后进入 他说这是cookie的测试网页&#xff0c;我们抓个包&#xff0c;得到cookie值 base64解码 扫描一下靶机ip的目录 发现http://192.168.88.153/backup/&#xff0c;访问 下载一下发现是他的网页源码 通过代码审计&#xff0c;发现 通过代码审计得知&…

JAVA进阶学习13

文章目录 2.2.3 综合输入和输出方法进行文件拷贝2.2.4 字节流读取时乱码的问题 2.3 字符流的方法概述2.3.1 FileReader方法2.3.2 FileWriter方法2.3.3 小结 三、高级IO流3.1 缓冲流3.1.1 字节缓冲流3.1.2 字符缓冲流 3.2 转换流3.3 序列化流3.3.1 序列化流3.3.2 反序列化流 3.4…

极简聊天室-websocket版

再写一个极简聊天室的websocket版&#xff0c;在本例中&#xff0c;websocket仅用于服务器向客户端传输信息&#xff0c;客户端向服务器发送信息是传统的http post方式&#xff0c;用axios来实现的&#xff0c;当然websocket本身是支持双向通信&#xff0c;主要是为了方便跟前面…

【leetcode详解】正方形中的最多点数【中等】(C++思路精析)

思路精析&#xff1a; 自定义结构体解读&#xff1a; 一个点是否在题给正方形中&#xff0c;只取决于其横纵坐标的最大值&#xff0c;记为dis 沟通二位数组points和字符串s的桥梁&#xff0c;就是这个点的序号&#xff0c;记为idx 由此自定义结构体&#xff0c;储存dis 和i…