前言

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

Redis 有事务吗

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

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

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

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

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

  1. 执行命令 multi 开启一个事务。
  2. 开启事务之后执行的命令都会被放入一个队列,如果成功之后会固定返回 QUEUED
  3. 执行命令 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事务还能用吗的更多相关文章

  1. NESTED内部事务异常会回滚 外部事务不会回滚 ;内部事务没有异常,外部事务有异常 则整体事务都回滚

    NESTED内部事务异常会回滚 外部事务不会回滚 :内部事务没有异常,外部事务有异常 则整体事务都回滚

  2. Spring事务为什么不会自动回滚?Spring事务怎样才会自动回滚?事务自动回滚条件及手动回滚

    原文:https://blog.csdn.net/qq_32331073/article/details/76508147 更多Spring事务问题请访问链接:Spring事务回滚问题疑难详解 在此, ...

  3. 05. redis事务

    目录 Redis 事务 事务 1. 命令有序 2. 始终原子 开启使用事务 Redis事务中出现错误 1. EXEC前的错误 2. EXEC后的错误 为什么出错了不支持roll backs? Redi ...

  4. 【高频 Redis 面试题】Redis 事务是否具备原子性?

    一.Redis 事务的实现原理 一个事务从开始到结束通常会经历以下三个阶段: 1.事务开始 客户端发送 MULTI 命令,服务器执行 MULTI 命令逻辑. 服务器会在客户端状态(redisClien ...

  5. MySQL(22):事务管理之 事务回滚

    1. 在操作事务的时候,如果发现当前事务操作是不合理的,此时只要还没有提交事务,就可以通过回滚取消当前事务,接下来就针对事务的回滚进行详细讲解. 2. 为了演示回滚操作,在上一个笔记案例基础之上,此时 ...

  6. Django数据库--事务及事务回滚

    数据库的读写操作中,事务在保证数据的安全性和一致性方面起着关键的作用,而回滚正是这里面的核心操作.Django的ORM在事务方面也提供了不少的API.有事务出错的整体回滚操作,也有基于保存点的部分回滚 ...

  7. Python ORM框架SQLAlchemy学习笔记之数据添加和事务回滚介绍

    1. 添加一个新对象 前面介绍了映射到实体表的映射类User,如果我们想将其持久化(Persist),那么就需要将这个由User类建立的对象实例添加到我们先前创建的Session会话实例中: 复制代码 ...

  8. 事务之二:spring事务(事务管理方式,事务5隔离级别,7个事务传播行为,spring事务回滚条件)

    事物管理对于企业应用来说是至关重要的,好使出现异常情况,它也可以保证数据的一致性. spring支持编程式事务管理和声明式事务管理两种方式. 编程式事务管理使用TransactionTemplate或 ...

  9. sql事务(Transaction)用法介绍及回滚实例

    sql事务(Transaction)用法介绍及回滚实例 事务(Transaction)是并发控制的单位,是用户定义的一个操作序列.这些操作要么都做,要么都不做,是一个不可分割的工作单位.通过事务, S ...

  10. Spring异常抛出触发事务回滚

    Spring.EJB的声明式事务默认情况下都是在抛出unchecked exception后才会触发事务的回滚 /** * 如果在spring事务配置中不为切入点(如这里的切入点可以定义成test*) ...

随机推荐

  1. 再见Windows C++

    我3年多以前写过一个小工具,是用来检测Windows操作系统的版本及其所安装的.NET Framework版本的,我用它来排查由于缺乏运行环境支持所导致的程序无法运行的问题.这个工具是用Visual ...

  2. 升级到VS2012,reportViewer无法使用

    最近公司的开发环境升级到VS2012,为了电脑能够快点,我重装系统,只装VS2012没有装VS2010.这个时候问题来了,原本用VS2010开发的项目用VS2012编译能通过,运行时包下图错: 为了解 ...

  3. css-transition和transform实现图片悬浮移动动画

    今天在京东首页上看到一个效果,鼠标悬浮在图片上时,图片发生移动,鼠标移走时再移回,并且有一个过渡效果. 貌似很简单,自己做做试试吧 我首先使用的是jquery在鼠标悬浮到图片上给图片增加一个类,这个类 ...

  4. jquery解析XML(1)

    jquery解析XML文件 html代码 <!DOCTYPE html><html><head><title>解析XML</title>&l ...

  5. Directx11学习笔记【二十二】 用高度图实现地形

    本文由zhangbaochong原创,转载请注明出处http://www.cnblogs.com/zhangbaochong/p/5827714.html 在前面我们曾经实现过简单的地形(Direct ...

  6. Laravel 用户验证Auth::attempt fail的问题

    1.在laravel项目中,当使用Auth::attempt()用于用户验证时,Auth::attempt()会把密码通过Hash进行转换,变成一串不知啥的长字符,如果你在数据库里事先设置了明文的密码 ...

  7. Android/Linux Thermal框架分析及其Governor对比

    图表 1 Thermal框架 随着SoC性能的快速提升,功耗也极大提高,带来的负面影响是SoC的温度提高很快,甚至有可能造成物理损坏.同时功耗浪费也降低了电池寿命. 从上图可知,Thermal框架可以 ...

  8. wpf 获取Image的图片并保存到本地

    XMAL代码如下: <Image Name="ImageToSave" Source="Images/pic_bg.png" Grid.RowSpan=& ...

  9. 【mybatis】mybatis中 &lt;if test=&gt;等于的条件怎么写

  10. Ubuntu系统的nginx启动

    在不同的linux系统中,安装nginx之后,要启动nginx,目录路径可能有一点不一样,如下是Ubuntu系统启动nginx,其他版本的linux系统可能不适用. Ubuntu安装之后的文件结构大致 ...