【精通Redis】Redis事务

news2024/11/16 7:37:41

文章目录

  • 前言
  • 一、标准事务
    • 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 命令监视 key1
            jedis.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 命令监视 key1
            jedis.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)) {
            // 模拟其他客户端修改 key1
            jedis.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.coloradmin.cn/o/1975571.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Python数据结构实战:列表、字典与集合的高效使用

前言 在编程中&#xff0c;选择合适的数据结构对于提高程序效率至关重要。本文将介绍Python中最常用的数据结构——列表&#xff08;list&#xff09;、字典&#xff08;dict&#xff09;和集合&#xff08;set&#xff09;&#xff0c;并探讨它们的内部实现以及如何高效地使用…

The operation was rejected by your operating system. code CERT_HAS_EXPIRED报错解决

各种报错&#xff0c;试了清缓存&#xff0c;使用管理员权限打开命令行工具&#xff0c;更新npm&#xff0c;都不好使 最终解决&#xff1a;删除 c:/user/admin/ .npmrc

我的最爱之《达明一派》

达明一派&#xff0c;是我最爱。刘以达(Tats)与黄耀明(Anthony Wong)在1980年代的香港组成的二人流行音乐组合&#xff0c;在90年代&#xff0c;网络还没兴起时&#xff0c;那是卡带流行的岁月。90年代&#xff0c;我与好友&#xff0c;同考大学&#xff0c;他留在了南充读读书…

世媒讯带您了解什么是媒体邀约

什么是媒体邀约&#xff1f;其实媒体邀约是一种公关策略&#xff0c;旨在通过邀请媒体记者和编辑参加特定的活动、发布会或其他重要事件&#xff0c;以确保这些活动能够得到广泛的报道和关注。通过这种方式&#xff0c;企业和组织希望能够传达重要信息&#xff0c;提高品牌知名…

网络监控软件的作用是什么|企业用的六款网络监控软件

网络监控软件是干什么的呢&#xff1f;它是用来管理网络安全的&#xff0c;尤其是对于企业而言至关重要&#xff0c;下面我为你推荐六款知名的网络监控软件。 1. 安企神 功能特点&#xff1a; 全面监控&#xff1a;提供电脑屏幕监控、文件操作监控、聊天记录监控等功能&#…

全开源图床系统源码

一款专为个人需求设计的高效图床解决方案&#xff0c;集成了强大的图片压缩功能与优雅的前台后台管理界面。 项目结构精简高效&#xff0c;提供自定义图片压缩率与尺寸设置&#xff0c;有效降低存储与带宽成本。 支持上传JPEG、PNG、GIF格式图片并转换为WEBP格式&#xff0c;…

算法:BFS 解决多源最短路问题

目录 多源最短路 题目一&#xff1a;矩阵 题目二&#xff1a;飞地的数量 题目三&#xff1a;地图中的最高点 题目四&#xff1a;地图分析 多源最短路 首先想要知道多源最短路&#xff0c;就先要明白单源最短路&#xff0c;bfs解决单源最短路问题前面学习过&#xff0c;单…

leetcode-二叉树oj题1(共三道)--c语言

目录 a. 二叉树的概念以及实现参照博客&#xff1a; 一、三道题的oj链接 二、每题讲解 1.单值二叉树 a. 题目&#xff1a; b. 题目所给代码 c. 思路 d. 代码&#xff1a; 2. 相同的树 a. 题目 b. 题目所给代码 c. 思路 d. 代码 3. 二叉树的前序遍历 a. 题目 b.…

J029_UDP通信

一、需求描述 实现UDP的通信 1.1 一发一收 1.1.1 ClientTest1 package com.itheima.udp;import java.net.*;import static java.net.InetAddress.*;//完成udp通信快速入门&#xff0c;实现一收一发 public class ClientTest1 {public static void main(String[] args) thro…

递归 35

方法递归 递归算法 package File;public class digui {public static void main(String[] args) {//猴子吃桃//f(10)1//f(n)-f(n)/2—1f&#xff08;n1&#xff09;//f(n)F(n1)2System.out.println(f(3));}public static int f(int n){if (n10){return 1;}else {return 2*f(n1)…

MEME币热潮结束了?上市成功率仅1.4%!迷因暴富梦醒?洗量超容易,热潮都是假?

近年来&#xff0c;随着加密货币行业的蓬勃发展&#xff0c;各种迷因币(meme coins)也在此浪潮之中纷纷崛起。然而&#xff0c;在专门用于创造迷因币的平台"pump.fun"上&#xff0c;绝大多数迷因币都无法真正成功发行和上市。 最新的数据显示&#xff0c;近日Solana迷…

golang国内proxy设置

go env -w GOPROXYhttps://goproxy.cn,direct经常使用的两个, goproxy.cn 和 goproxy.io 连接分别是 https://goproxy.cn https://goproxy.io 如果遇到某些包下载不下来的情况&#xff0c;可尝试更换数据源 更推荐使用https://goproxy.cn 速度快&#xff0c;缓存的包多 提醒…

树莓派5进行YOLOv8部署的4种不同部署方式检测速度对比:pytorch、onnx、ncnn、tflite

《博主简介》 小伙伴们好&#xff0c;我是阿旭。专注于人工智能、AIGC、python、计算机视觉相关分享研究。 ✌更多学习资源&#xff0c;可关注公-仲-hao:【阿旭算法与机器学习】&#xff0c;共同学习交流~ &#x1f44d;感谢小伙伴们点赞、关注&#xff01; 《------往期经典推…

关于k8s集群的资源发布方式(灰度/滚动发布)

目录 1.常见的发布方式 2.实现蓝绿发布 3.实现金丝雀发布&#xff08;Canary Release&#xff09; 4.声明式管理方法 1.常见的发布方式 蓝绿发布:两套环境交替升级&#xff0c;旧版本保留一定时间便于回滚优点&#xff1a;用户无感知&#xff0c;部署和回滚速度较快&#…

如何统计visiual studio代码行数

统计Visual Studio中的代码行数&#xff0c;可以通过Visual Studio自带的查找功能结合正则表达式来实现。以下是一个详细的步骤说明&#xff1a; 一、使用Visual Studio的查找功能 打开Visual Studio&#xff1a;首先&#xff0c;确保你已经打开了Visual Studio并加载了你想要…

pyqt中使用opengl绘制图像

首先有在C中使用的opengl基础 在qt designer中&#xff0c;可以直接找到 QOpenGLWidget类 &#xff0c;是一个黑框框&#xff08;图1&#xff09; 也就是说&#xff0c;不需要安装额外的东西&#xff0c;一切从简 然后就是看官方文档学习啦 intializeGL() 在这里面设定好…

JavaScript基础——数据类型转换

显示数据类型转换 String()函数进行显示转换 Number()函数进行显示转换 Boolean()函数进行显示转换 隐式数据类型转换 算术运算隐式转化 比较操作隐式转化 赋值操作 在JavaScript中&#xff0c;数据类型转换是常见的操作&#xff0c;它允许将一种类型的数据转换为另一种…

c++网络编程实战——开发基于协议的文件传输模块(一)如何实现一个简单的tcp长连接

前言 在之前的几篇内容中我们已经介绍过基于ftp协议的文件传输模块&#xff0c;而这个系列我们所想实现的就是如何实现基于tcp进行的文件传输模块,话不多说&#xff0c;开坑开坑! 什么是tcp长连接 我们知道tcp在建立连接的时候会通过三次握手与四次挥手来建立tcp连接&#x…

用uniapp 及socket.io做一个简单聊天app 4

界面如下&#xff1a; <template><view class"container"><input v-model"username" placeholder"用户名" /><input v-model"password" type"password" placeholder"密码" /><butto…

探秘北京崇文门中医医院卫景沛医生:为何深受患者信赖?

卫景沛是北京崇文门中医医院特聘专家&#xff0c;深受患者信赖&#xff01; 北京崇文门中医医院卫景沛主任毕业于兰州大学医学院&#xff0c;拥有医学硕士学位。他的硕士导师是天坛医院的王拥军教授&#xff0c;主要研究方向为脑血管病及脑血管病介入治疗。 为了提升自己在缺血…