不能回滚的Redis事务还能用吗
前言
事务是关系型数据库的特征之一,那么作为 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
,一旦我们执行了一些如 set
,sadd
等能修改 key
值对应 value
的命令,那么客户端的 CLIENT_DIRTY_CAS
标记属性将会被修改,后面执行事务提交命令 exec
时发现客户端的标记属性被修改过(乐观锁的体现),则会拒绝执行事务。
总结
本文主要介绍了 Redis
当中的事务机制,在介绍事务实现原理的同时从传统关系型数据库的 ACID
四大特性对比分析了 Redis
当中的事务,并最终了解到了 Redis
的事务似乎并不是那么“完美”。
不能回滚的Redis事务还能用吗的更多相关文章
- NESTED内部事务异常会回滚 外部事务不会回滚 ;内部事务没有异常,外部事务有异常 则整体事务都回滚
NESTED内部事务异常会回滚 外部事务不会回滚 :内部事务没有异常,外部事务有异常 则整体事务都回滚
- Spring事务为什么不会自动回滚?Spring事务怎样才会自动回滚?事务自动回滚条件及手动回滚
原文:https://blog.csdn.net/qq_32331073/article/details/76508147 更多Spring事务问题请访问链接:Spring事务回滚问题疑难详解 在此, ...
- 05. redis事务
目录 Redis 事务 事务 1. 命令有序 2. 始终原子 开启使用事务 Redis事务中出现错误 1. EXEC前的错误 2. EXEC后的错误 为什么出错了不支持roll backs? Redi ...
- 【高频 Redis 面试题】Redis 事务是否具备原子性?
一.Redis 事务的实现原理 一个事务从开始到结束通常会经历以下三个阶段: 1.事务开始 客户端发送 MULTI 命令,服务器执行 MULTI 命令逻辑. 服务器会在客户端状态(redisClien ...
- MySQL(22):事务管理之 事务回滚
1. 在操作事务的时候,如果发现当前事务操作是不合理的,此时只要还没有提交事务,就可以通过回滚取消当前事务,接下来就针对事务的回滚进行详细讲解. 2. 为了演示回滚操作,在上一个笔记案例基础之上,此时 ...
- Django数据库--事务及事务回滚
数据库的读写操作中,事务在保证数据的安全性和一致性方面起着关键的作用,而回滚正是这里面的核心操作.Django的ORM在事务方面也提供了不少的API.有事务出错的整体回滚操作,也有基于保存点的部分回滚 ...
- Python ORM框架SQLAlchemy学习笔记之数据添加和事务回滚介绍
1. 添加一个新对象 前面介绍了映射到实体表的映射类User,如果我们想将其持久化(Persist),那么就需要将这个由User类建立的对象实例添加到我们先前创建的Session会话实例中: 复制代码 ...
- 事务之二:spring事务(事务管理方式,事务5隔离级别,7个事务传播行为,spring事务回滚条件)
事物管理对于企业应用来说是至关重要的,好使出现异常情况,它也可以保证数据的一致性. spring支持编程式事务管理和声明式事务管理两种方式. 编程式事务管理使用TransactionTemplate或 ...
- sql事务(Transaction)用法介绍及回滚实例
sql事务(Transaction)用法介绍及回滚实例 事务(Transaction)是并发控制的单位,是用户定义的一个操作序列.这些操作要么都做,要么都不做,是一个不可分割的工作单位.通过事务, S ...
- Spring异常抛出触发事务回滚
Spring.EJB的声明式事务默认情况下都是在抛出unchecked exception后才会触发事务的回滚 /** * 如果在spring事务配置中不为切入点(如这里的切入点可以定义成test*) ...
随机推荐
- 再见Windows C++
我3年多以前写过一个小工具,是用来检测Windows操作系统的版本及其所安装的.NET Framework版本的,我用它来排查由于缺乏运行环境支持所导致的程序无法运行的问题.这个工具是用Visual ...
- 升级到VS2012,reportViewer无法使用
最近公司的开发环境升级到VS2012,为了电脑能够快点,我重装系统,只装VS2012没有装VS2010.这个时候问题来了,原本用VS2010开发的项目用VS2012编译能通过,运行时包下图错: 为了解 ...
- css-transition和transform实现图片悬浮移动动画
今天在京东首页上看到一个效果,鼠标悬浮在图片上时,图片发生移动,鼠标移走时再移回,并且有一个过渡效果. 貌似很简单,自己做做试试吧 我首先使用的是jquery在鼠标悬浮到图片上给图片增加一个类,这个类 ...
- jquery解析XML(1)
jquery解析XML文件 html代码 <!DOCTYPE html><html><head><title>解析XML</title>&l ...
- Directx11学习笔记【二十二】 用高度图实现地形
本文由zhangbaochong原创,转载请注明出处http://www.cnblogs.com/zhangbaochong/p/5827714.html 在前面我们曾经实现过简单的地形(Direct ...
- Laravel 用户验证Auth::attempt fail的问题
1.在laravel项目中,当使用Auth::attempt()用于用户验证时,Auth::attempt()会把密码通过Hash进行转换,变成一串不知啥的长字符,如果你在数据库里事先设置了明文的密码 ...
- Android/Linux Thermal框架分析及其Governor对比
图表 1 Thermal框架 随着SoC性能的快速提升,功耗也极大提高,带来的负面影响是SoC的温度提高很快,甚至有可能造成物理损坏.同时功耗浪费也降低了电池寿命. 从上图可知,Thermal框架可以 ...
- wpf 获取Image的图片并保存到本地
XMAL代码如下: <Image Name="ImageToSave" Source="Images/pic_bg.png" Grid.RowSpan=& ...
- 【mybatis】mybatis中 <;if test=>;等于的条件怎么写
- Ubuntu系统的nginx启动
在不同的linux系统中,安装nginx之后,要启动nginx,目录路径可能有一点不一样,如下是Ubuntu系统启动nginx,其他版本的linux系统可能不适用. Ubuntu安装之后的文件结构大致 ...