Redis事务为什么不支持回滚

目录
  • 前言
  • Redis 有事务吗
  • Redis 事务实现原理
  • Redis 事务 ACID 特性
    • A - 原子性
    • C - 一致性
    • I - 隔离性
    • D - 持久性
  • watch 命令
    • watch 命令的作用
    • watch 原理分析
  • 总结

前言

事务是关系型数据库的特征之一,那么作为 Nosql 的代表 Redis 中有事务吗?如果有,那么 Redis 当中的事务又是否具备关系型数据库的 ACID 四大特性呢?

Redis 有事务吗

这个答案可能会令很多人感到意外,Redis 当中是存在“事务”的。这里我把 Redis 的事务带了引号,原因在后面分析。

Redis 当中的单个命令都是原子操作,但是如果我们需要把多个命令组合操作又需要保证数据的一致性时,就可以考试使用 Redis 提供的事务(或者使用前面介绍的 Lua 脚本)。

Redis 当中,通过下面 4 个命令来实现事务:

  • multi:开启事务
  • exec:执行事务
  • discard:取消事务
  • watch:监视

Redis 的事务主要分为以下 3 步:

  • 执行命令 multi 开启一个事务。
  • 开启事务之后执行的命令都会被放入一个队列,如果成功之后会固定返回 QUEUED
  • 执行命令 exec 提交事务之后,Redis 会依次执行队列里面的命令,并依次返回所有命令结果(如果想要放弃事务,可以执行 discard 命令)。

接下来让我们依次执行以下命令来体会一下 Redis 当中的事务:

multi //开启事务
set name lonely_wolf //设置 name,此时 Redis 会将命令放入队列
set age 18  //设值 age,此时 Redis 会将命令放入队列
get name  //获取 name,此时 Redis 会将命令放入队列
exec //提交事务,此时会依次执行队列里的命令,并依次返回结果

执行完成之后得到如下效果:

Redis 事务实现原理

Redis 中每个客户端都有记录当前客户端的事务状态 multiState,下面就是一个客户端 client 的数据结构定义:

typedef struct client {
    uint64_t id;//客户端唯一 id
    multiState mstate; //MULTI 和 EXEC 状态(即事务状态)
    //...省略其他属性
} client;

multiState 数据结构定义如下:

typedef struct multiState {
    multiCmd *commands;//存储命令的 FIFO 队列
    int count;//命令总数
    //...省略了其他属性
} multiState;

multiCmd 是一个队列,用来接收并存储开启事务之后发送的命令,其数据结构定义如下:

typedef struct multiCmd {
    robj **argv;//用来存储参数的数组
    int argc;//参数的数量
    struct redisCommand *cmd;//命令指针
} multiCmd;

我们以上面事务的示例截图中事务为例,可以得到如下所示的一个简图:

Redis 事务 ACID 特性

传统的关系型数据库中,一个事务一般都具有 ACID 特性。那么现在就让我们来分析一下 Redis 是否也满足这 ACID 四大特性。

A - 原子性

在讨论事务的原子性之前,我们先来看 2 个例子。

模拟事务在执行命令前发生异常。依次执行以下命令:

multi //开启事务
set name lonely_wolf //设置 name,此时 Redis 会将命令放入队列
get  //执行一个不完成的命令,此时会报错
exec //在发生异常后提交事务

最终得到了如下图所示的结果,我们可以看到,当命令入队的时候报错时,事务已经被取消了:

模拟事务在执行命令前发生异常。依次执行以下命令:

flushall //为了防止影响,先清空数据库
multi //开启事务
set name lonely_wolf //设置 name,此时 Redis 会将命令放入队列
incr name  //这个命令只能用于 value 为整数的字符串对象,此时执行会报错
exec //提交事务,此时在执行第一条命令成功,执行第二条命令失败
get name //获取 name 的值

最终得到了如下图所示的结果,我们可以看到,当执行事务报错的时候,之前已经成功的命令并没有被回滚,也就是说在执行事务的时候某一个命令失败了,并不会影响其他命令的执行,即 Redis 的事务并不会回滚

Redis 中的事务为什么不会滚

这个问题的答案在 Redis 官网中给出了明确的解释:

总结起来主要就是 3 个原因:

  • Redis 作者认为发生事务回滚的原因大部分都是程序错误导致,这种情况一般发生在开发和测试阶段,而生产环境很少出现。
  • 对于逻辑性错误,比如本来应该把一个数加 1 ,但是程序逻辑写成了加 2,那么这种错误也是无法通过事务回滚来进行解决的。
  • Redis 追求的是简单高效,而传统事务的实现相对比较复杂,这和 Redis 的设计思想相违背。

C - 一致性

一致性指的就是事务执行前后的数据符合数据库的定义和要求。这一点 Redis 中的事务是符合要求的,上面讲述原子性的时候已经提到,不论是发生语法错误还是运行时错误,错误的命令均不会被执行。

I - 隔离性

事务中的所有命令都会按顺序执行,在执行 Redis 事务的过程中,另一个客户端发出的请求不可能被服务,这保证了命令是作为单独的独立操作执行的。所以 Redis 当中的事务是符合隔离性要求的。

D - 持久性

如果 Redis 当中没有被开启持久化,那么就是纯内存运行的,一旦重启,所有数据都会丢失,此时可以认为 Redis 不具备事务的持久性;而如果 Redis 开启了持久化,那么可以认为 Redis 在特定条件下是具备持久性的。

watch 命令

上面我们讲述 Redis 中事务时,提到的的常用命令还有一个 watch 命令,这个又是做什么用的呢?我们还是先来看一个例子。

首先打开一个客户端一,依次执行以下命令:

flushall  //清空数据库
multi     //开启事务
get name  //获取 name,此时正常返回 nil
set name lonely_wolf //设置 name
get name //获取 name,此时正常应该返回 lonely_wolf

得到如下效果图:

这时候我们先不执行事务,打开另一个客户端二,来执行一个命令 set name zhangsan

客户端二执行成功了,这时候再返回到客户端一执行 exec 命令:

可以发现,第一句话返回了 zhangsan。也就是说,name 这个 key 值在入队之后到 exec 之前发生了变化,一旦发生这种情况,可能会引起很严重的问题,所以在关系型数据库可以通过锁来解决这种问题,那么 Redis 当中试如何解决的呢?

是的,在 Redis 当中就是通过 watch 命令来处理这种场景的。

watch 命令的作用

watch 命令可以为 Redis 事务提供 CAS 乐观锁行为,它可以在 exec 命令执行之前,监视任意 key 值的变化,也就是说当多个线程更新同一个 key 值的时候,会跟原值做比较,一旦发现它被修改过,则拒绝执行命令,并且会返回 nil 给客户端。

下面还是让我们通过一个示例来演示一下。

打开一个客户端一,依次执行如下命令:

flushall  //清空数据库
watch name //监视 name
multi     //开启事务
set name lonely_wolf //设置 name
set age 18 // 设置 age
get name   //获取 name
get age    //获取 age

执行之后得到如下效果图:

这时候再打开一个客户端二,执行 set name zhangsan命令:

然后再回到客户端一执行 exec命令。这时候会发现直接返回了 nil,也就是事务中所有的命令都没有被执行(即:只要检测到一个 key 值被修改过,那么整个事务都不会被执行):

watch 原理分析

下面是一个 Redis 服务的数据结构定义:

typedef struct redisDb {
    dict *watched_keys;  //被 watch 命令监视的 key
    int id;           //Database ID
    //...省略了其他属性
} redisDb;

可以看到,redisDb 中的 watched_keys 存储了一个字典,这个字典当中的 key 存的就是被监视的 key ,然后字典的值存的就是客户端 id。然后每个客户端还有一个标记属性 CLIENT_DIRTY_CAS,一旦我们执行了一些如 setsadd 等能修改 key 值对应 value 的命令,那么客户端的 CLIENT_DIRTY_CAS 标记属性将会被修改,后面执行事务提交命令 exec 时发现客户端的标记属性被修改过(乐观锁的体现),则会拒绝执行事务。

总结

本文主要介绍了 Redis 当中的事务机制,在介绍事务实现原理的同时从传统关系型数据库的 ACID 四大特性对比分析了 Redis 当中的事务,并最终了解到了 Redis 的事务似乎并不是那么“完美”。

到此这篇关于Redis事务为什么不支持回滚 的文章就介绍到这了,更多相关Redis事务回滚 内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • redis中的事务操作案例分析

    本文实例讲述了redis中的事务操作.分享给大家供大家参考,具体如下: redis与mysql的事务 Redis支持简单的事务 简单使用 讲张三的100圆钱转账给lisi: set zhangsan 800 set lisi 100 multi decrby zhangsan 100 incrby lisi 100 exec 失败的两种情况 在mutil后面的语句中, 语句出错可能有2种情况,还是以转账的情况来分析: (1)语法就有问题 127.0.0.1:6379> multi OK 127.

  • Redis 事务知识点相关总结

    Redis中的事务介绍     MySQL中的事务大家都不陌生,Redis中的事务和MySQL中的事务不同,今天看下Redis事务中的一些知识点吧. 01 事务简介 Redis中的事务使用multi.exec来标记,其中multi代表事务开始,exec代表事务结束,multi和exec之间的命令是原子顺序执行的.下面是一个例子: 127.0.0.1:7397> multi OK 127.0.0.1:7397> set key_hello hello QUEUED 127.0.0.1:7397&

  • Redis 基础教程之事务的使用方法

    Redis 基础教程之事务的使用方法 Redis 事务可以一次执行多个命令, 并且带有以下两个重要的保证: 事务是一个单独的隔离操作:事务中的所有命令都会序列化.按顺序地执行.事务在执行的过程中,不会被其他客户端发送来的命令请求所打断. 事务是一个原子操作:事务中的命令要么全部被执行,要么全部都不执行. 一个事务从开始到执行会经历以下三个阶段: 开始事务. 命令入队. 执行事务. 实例 以下是一个事务的例子, 它先以 MULTI 开始一个事务, 然后将多个命令入队到事务中, 最后由 EXEC 命

  • Redis 事务与过期时间详细介绍

    Redis 事务与过期时间详细介绍 一.Redis事务: Redis中支持事务,事务即为当我们需要执行几条命令时,要么这几条命令都不执行,要么都执行: 1.开始事务写入: multi 2.然后写入命令,注意写完事务要执行的每条命令之后回车即可,命令会自动入队: lpush art:1 hello lpush art:1 nihao 3.执行事务: exec Redis则会保证事务中的所有命令要么都执行,要么都不执行. 二.Redis过期时间: 实际开发中经常会遇到一些有时效性的数据,比如缓存,过

  • Redis事务为什么不支持回滚

    目录 前言 Redis 有事务吗 Redis 事务实现原理 Redis 事务 ACID 特性 A - 原子性 C - 一致性 I - 隔离性 D - 持久性 watch 命令 watch 命令的作用 watch 原理分析 总结 前言 事务是关系型数据库的特征之一,那么作为 Nosql 的代表 Redis 中有事务吗?如果有,那么 Redis 当中的事务又是否具备关系型数据库的 ACID 四大特性呢? Redis 有事务吗 这个答案可能会令很多人感到意外,Redis 当中是存在"事务"的

  • 基于Postgresql 事务的提交与回滚解析

    用过oracle或mysql的人都知道在sqlplus或mysql中,做一个dml语句,如果发现做错了,还可以rollback;掉,但在PostgreSQL的psql中,如果执行一个dml,没有先运行begin;的话,一执行完就马上提交了,不能回滚,这样容易导致误操作的发生,有没有什么办法避免这个风险呢? 当然有,在psql中默认是打开自动提交的,我们可以关闭自动提交,方法如下: 设置\set AUTOCOMMIT off test=# create table test1 (x int); C

  • Spring事务捕获异常后依旧回滚的解决

    目录 前沿 问题阐述 知识点前置条件 问题追踪 总结 前沿 一段生产事故发人深省,在Spring的声明式事务中手动捕获异常,居然判定回滚了,这是什么操作?话不多说直接上代码 @Service public class A {     @Autowired     private B b;     @Autowired     private C c;     @Transactional(propagation = Propagation.REQUIRED, isolation = Isolat

  • 详解Java的JDBC API中事务的提交和回滚

    如果JDBC连接是在自动提交模式下,它在默认情况下,那么每个SQL语句都是在其完成时提交到数据库. 这可能是对简单的应用程序,但有三个原因,你可能想关闭自动提交和管理自己的事务: 为了提高性能 为了保持业务流程的完整性 使用分布式事务 若要控制事务,以及何时更改应用到数据库.它把单个SQL语句或一组SQL语句作为一个逻辑单元,而且如果任何语句失败,整个事务失败. 若要启用,而不是JDBC驱动程序默认使用auto-commit模式手动事务支持,使用Connection对象的的setAutoComm

  • mysql实现事务的提交与回滚的实例详解

    最近要对数据库的数据进行一个定时迁移,为了防止在执行过程sql语句因为某些原因报错而导致数据转移混乱,因此要对我们的脚本加以事务进行控制. 首先我们建一张tran_test表 CREATE TABLE tran_test( f1 VARCHAR(10) NOT NULL, f2 INT(1) DEFAULT NULL, PRIMARY KEY (f1) )ENGINE=INNODB CHARSET=utf8 我想对tran_test插入两条数据,但是为了防止插入中报错,因此我要把插入语句控制在一

  • golang实现mysql数据库事务的提交与回滚

    MySQL 事务主要用于处理操作量大,复杂度高的数据.在 MySQL 中只有使用了 Innodb 数据库引擎的数据库或表才支持事务. 事务用来管理 insert,update,delete 语句,事务处理可以用来维护数据库的完整性,保证成批的 SQL 语句要么全部执行,要么全部不执行. 一般来说,事务是必须满足4个条件(ACID)::原子性(Atomicity,或称不可分割性).一致性(Consistency).隔离性(Isolation,又称独立性).持久性(Durability). 本文主要

  • mysql实现事务的提交和回滚实例

    mysql创建存储过程的官方语法为: 复制代码 代码如下: START TRANSACTION | BEGIN [WORK]COMMIT [WORK] [AND [NO] CHAIN] [[NO] RELEASE]ROLLBACK [WORK] [AND [NO] CHAIN] [[NO] RELEASE]SET AUTOCOMMIT = {0 | 1} 我这里要说明的mysql事务处理多个SQL语句的回滚情况.比如说在一个存储过程中启动一个事务,这个事务同时往三个表中插入数据,每插完一张表需要

  • 一篇文章带你彻底搞懂Redis 事务

    目录 Redis 事务简介 Redis 事务基本指令 实例分析 Redis 事务与 ACID 总结 Redis 事务简介 Redis 只是提供了简单的事务功能.其本质是一组命令的集合,事务支持一次执行多个命令,在事务执行过程中,会顺序执行队列中的命令,其他客户端提交的命令请求不会插入到本事务执行命令序列中.命令的执行过程是顺序执行的,但不能保证原子性.无法像 MySQL 那样,有隔离级别,出了问题之后还能回滚数据等高级操作.后面会详细分析. Redis 事务基本指令 Redis 提供了如下几个事

  • 完美解决Spring声明式事务不回滚的问题

    疑问,确实像往常一样在service上添加了注解 @Transactional,为什么查询数据库时还是发现有数据不一致的情况,想想肯定是事务没起作用,出现异常的时候数据没有回滚.于是就对相关代码进行了一番测试,结果发现一下踩进了两个坑,确实是事务未回滚导致的数据不一致. 下面总结一下经验教训: Spring事务的管理操作方法 编程式的事务管理 实际应用中很少使用 通过使用TransactionTemplate 手动管理事务 声明式的事务管理 开发中推荐使用(代码侵入最少) Spring的声明式事

  • java事务回滚失败问题分析

    Spring-Java事物回滚失效处理最近在做项目中,无意间发现有个类在抛事物回滚操作,数据也正常的插入到数据库当中了,于是仔细查看看一下具体原因. 一切还是要从Java的检查型异常和非检查型异常说起. 那么什么是检查型异常什么又是非检查型异常呢? 最简单的判断点有两个: 1.继承自RuntimeException或Error的是非检查型异常,而继承自Exception的则是检查型异常(当然,RuntimeException本身也是Exception的子类). 2.对非检查型类异常可以不用捕获,

随机推荐