Redis 事务简介#

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

Redis 事务基本指令#

Redis 提供了如下几个事务相关的基础指令。

  • MULTI开启事务,Redis 会将后续命令加到队列中,而不真正执行它们,直到后续使用EXEC来原子化的顺序执行这些命令
  • EXEC执行所有事务块内的命令
  • DISCARD取消事务,放弃执行事务块内所有的命令
  • WATCH监视一个或多个 key,若事务在执行前,这些 key 被其他命令修改,则事务被终端,不会执行事务中的任何命令
  • UNWATCH取消 WATCH命令对所有 keys 的监视

一般情况下,一个简单的 Redis 事务主要分为如下几个部分:

  1. 执行命令MULTI开启一个事务。
  2. 开启事务之后,执行命令的多个命令会依次被放入一个队列,放入成功则会返回QUEUED消息。
  3. 执行命令EXEC提交事务,Redis 会依次执行队列中的命令,并依次返回所有命令的结果。(若想放弃提交事务,则执行DISCARD)。

下图简单介绍了下 Redis 事务执行的过程:

实例分析#

下面我们来通过一些实际具体例子,来体会下 Redis 中的事务。前面我们也说到 Redis 的事务不是正真的事务,是无法完全满足标准事务的ACID特性的。通过下面的例子,我们来看看,Redis 的“破产版”事务到底存在什么问题。

  • [A]正常执行提交
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> SET a 1
QUEUED
127.0.0.1:6379> SET b 2
QUEUED
127.0.0.1:6379> EXEC
1) OK
2) OK
127.0.0.1:6379> GET a
"1"
127.0.0.1:6379> GET b
"2"

开启事务后,提交的命令都会加入队列(QUEUED),执行 EXEC 后会逐步执行命令并返回结果。这个看起来是不是和我们平时使用 MySQL 的事务操作相似,类似 start transaction 和 commit。

  • [B]正常取消事务
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> SET a 1
QUEUED
127.0.0.1:6379> SET b 2
QUEUED
127.0.0.1:6379> DISCARD
OK
127.0.0.1:6379>
127.0.0.1:6379> GET a
(nil)
127.0.0.1:6379> GET b
(nil)

开启事务后,若不想继续事务,使用 DISCARD 取消,前面提交的命令并不会真正执行,相关的 key 值不变。这个看起来也和 MySQL 的事务相似,类似 start transaction 和 rollback。

  • [C]WATCH 监视 key
-- 线程 1 中执行
127.0.0.1:6379> del a
(integer) 1
127.0.0.1:6379> get a
(nil)
127.0.0.1:6379> SET a 0
OK
127.0.0.1:6379> WATCH a
OK
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> SET a 1
QUEUED
----------------------------------------- 线程 2 中执行
----------------------------------------- 127.0.0.1:6379> SET a 2
----------------------------------------- OK
127.0.0.1:6379> EXEC
(nil)
127.0.0.1:6379> GET a
"2"

在开启事务之前 WATCH 了 a 的值,随后再开启事务。在另一个线程中设置了 a 的值(SET a 2),然后再 EXEC 执行事务,结果为 nil,
说明事务没有被执行。因为 a 的值在 WATCH 之后发生了变化,所以事务被取消了。

需要注意的是,这里和开启事务的时间点没有关系,与 MULTI 和另一个线程设置 a 的值的先后没有关系。只要是在 WATCH 之后发生了变化。无论事务是否已经开启,执行事务(EXEC)的时候都会取消。
普通情况下,在执行 EXEC 和 DISCARD 命令时,都会默认执行 UNWATCH。

  • [D]语法错误
127.0.0.1:6379> SET a 1
OK
127.0.0.1:6379> SET b 2
OK
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> SET a 11
QUEUED
127.0.0.1:6379> SETS b 22
(error) ERR unknown command 'SETS'
127.0.0.1:6379> EXEC
(error) EXECABORT Transaction discarded because of previous errors.
127.0.0.1:6379> GET a
"1"
127.0.0.1:6379> GET b
"2"

当 Redis 开启一个事务后,若添加的命令中有语法错误,会导致事务提交失败。这种情况下事务队列中的命令都不会被执行。如上面例子中 a 和 b 的值都是原有的值。
这类在 EXEC 之前产生的错误,如命令名称错误,命令参数错误等,会在 EXEC 执行之前被检测出来,所以在发生这些错误的时候,事务会被取消,事务中的所有命令都不会执行。(这种情况看起来是不是有点像回滚了)

  • [E]运行时错误
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> SET a 1
QUEUED
127.0.0.1:6379> SET b hello
QUEUED
127.0.0.1:6379> INCR b
QUEUED
127.0.0.1:6379> EXEC
1) OK
2) OK
3) (error) ERR value is not an integer or out of range
127.0.0.1:6379> GET a
"1"
127.0.0.1:6379> GET b
"hello"

当 Redis 开启一个事务后,添加的命令没有出现前面说的语法错误,但是在运行时检测到了类型错误,导致事务最提交失败(说未完全成功可能更准确点)。此时事务并不会回滚,而是跳过错误命令继续执行。
如上面的例子,未报错的命令值已经修改,a 被设置成了 1,b 被设置为了 hello,但是报错的值未被修改,即 INCR b 类型错误,并未执行,b 的值也没有被再更新。

Redis 事务与 ACID#

通过上面的例子,我们已经知道 Redis 的事务和我们通常接触的 MySQL 等关系数据库的事务还有有一定差异的。它不保证原子性。同时 Redis 事务也没有事务隔离级别的概念。下面我们来具体看下 Redis 在 ACID 四个特性中,那些是满足的,那些是不满足的。
事务执行可以分为命令入队(EXEC 执行前)和命令实际执行(EXEC 执行之后)两个阶段。下面我们在分析的时候,很多时候都会分这两种情况来分析。

  • 原子性(A)

上面的实例分析中,[A],[B],[C]三种正常的情况,我们可以很明显的看出,是保证了原子性的
但是一些异常情况下,是不满足原子性的。

  1. 如 [D] 所示的情况,客户端发送的命令有语法错误,在命令入队列时 Redis 就判断出来了。等到执行 EXEC 命令时,Redis 就会拒绝执行所有提交的命令,返回事务失败的结果。此种情况下,事务中的所有命令都不会被执行了,是保证了原子性的
  2. 如 [E] 所示的情况,事务操作入队时,命令和操作类型不匹配,此时 Redis 没有检查出错误(这类错误是运行时错误)。等到执行 EXEC 命令后,Redis 实际执行这些命令操作时,就会报错。需要注意的是,虽然 Redis 会对错误的命令报错不执行,但是其余正确的命令会依次执行完。此种情况下,是无法保证原子性的
  3. 在执行事务的 EXEC 命令时,Redis 实例发生了故障,导致事务执行失败。此时,如果开启了 AOF 日志,那么只会有部分事务操作被记录到 AOF 日志中。使用redis-check-aof工具检测 AOF 日志文件,可以把未完成的事务操作从 AOF 文件中去除。这样一来,使用 AOF 文件恢复实例后,事务操作不会被再执行,从而保证了原子性。若使用的 RDB 模式,最新的 RDB 快照是在 EXEC 执行之前生成的,使用快照恢复之后,事务中的命令也都没有执行,从而保证了原子性。若 Redis 没有开启持久化,则重启后内存中的数据全部丢失,也就谈不上原子性了。
  • 一致性(C)

一致性指的是事务执行前后,数据符合数据库的定义和要求。这点在 Redis 事务中是满足的,不论是发生语法错误还是运行时错误,错误的命令均不会被执行。

  1. EXEC 执行之前,入队报错(实例分析中的语法错误)

事务会放弃执行,故可以保证一致性。

  1. EXEC 执行之后,实际执行时报错(实例分析中的运行时错误)

错误的命令不会被执行,正确的命令被执行,一致性可以保证。

  1. EXEC 执行时,实例宕机

若 Redis 没有开启持久化,实例宕机重启后,数据都没有了,数据是一致的。
若配置了 RDB 方式,RDB 快照不会在事务执行时执行。所以,若事务执行到一半,实例发生了故障,此时上一次 RDB 快照中不会包含事务所做的修改,而下一次 RDB 快照还没有执行,实例重启后,事务修改的数据会丢失,数据是一致的。若事务已经完成,但新一次的 RDB 快照还没有生成,那事务修改的数据也会丢失,数据也是一致的。
若配置了 AOF 方式。当事务操作还没被记录到 AOF 日志时,实例就发生故障了,使用 AOF 日志恢复后数据是一致的。若事务中的只有部分操作被记录到 AOF 日志,可以使用 redis-check-aof清除事务中已经完成的操作,数据库恢复后数据也是一致的。

  • 隔离性(I)

    1. 并发操作在 EXEC 执行前,隔离性需要通过 WATCH 机制来保证
    2. 并发操作在 EXEC 命令之后,隔离性可以保证

情况 a 可以参考前面的实例分析 WATCH 命令的使用。
情况 b,由于 Redis 是单线程执行命令,EXEC 命令执行后,Redis 会保证先把事务队列中的所有命令执行完之后再执行之后的命令。

  • 持久性(D)

若 Redis 没有开启持久化,那么就是所有数据都存储在内存中,一旦重启,数据就会丢失,因此此时事务的持久性是肯定无法得到保证的。
若 Redis 开启了持久化,当实例宕机重启,还是会有可能丢失数据,因此也并能完全保证持久性。
因此,我们可以说 Redis 事务无法一定保证持久性,仅在特殊的情况下,可以保证持久性。

关于 Redis 在开启持久化之后,为啥还会丢失数据,笔者会单独整理一篇 Redis 持久化与主从相关的文章来介绍,此处简单说下。
如果配置了 RDB 模式,在一个事务执行后,下一次 RDB 快照还未执行前,Redis 实例发生了宕机,数据就会丢失、
如果配置了 AOF 模式,而 AOF 模式的三种配置选项 no,everysec,always 也都可能会产生数据丢失的情况。

总结一下,Redis 事务对 ACID 的支持情况:

  • 具备一定的原子性,但不支持回滚
  • 满足一致性
  • 满足隔离性
  • 无法保证持久性

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

看一下官网的的说明:

What about rollbacks?
Redis does not support rollbacks of transactions since supporting rollbacks would have a significant impact on the simplicity and performance of Redis.

大部分需要事务回滚的情况是程序错误导致的,这种情况一般是开发环境,生产环境不应该出现这种错误。
对于逻辑错误,例如应该加 1,结果写成了加 2,这种情况无法通过回滚来解决。
Redis 追求的是简单高效,而传统事务的实现相对复杂很多,这和 Redis 的设计思想是违背的。当我们享受 Redis 的快速时,也就无法再要求它更多。

总结#

本文主要介绍了 Redis 事务的基础指令与执行流程,并分析了其对传统 ACID 特性支持的情况,相信大家对 Redis 事务已经有了一个简单的了解。
通过上面的介绍,会发现 Redis 的事务似乎有点鸡肋,确实实际中也很少会使用。至于事务的具体实现,笔者后续文章会结合源码进行分析。今天的文章就到这里,下期我们接着学。

说说 Redis 事务相关推荐

  1. redis学习之——Redis事务(transactions)

    Redis事务:可以一次执行多个命令,本质是一组命令的集合.一个事务中的,所有命令都会序列化,按顺序地串行化执行而不会被其它命令插入,不许加塞. 常用命令:MULTI  开启事务  EXEC 提交事务 ...

  2. 腾讯二面:Redis 事务支持 ACID 么?

    ❝ 腾讯面试官:「数据库事务机制了解么?」 「内心独白:小意思,不就 ACID 嘛,转眼一想,我面试的可是技术专家,不会这么简单的问题吧」 程许远:「balabala-- 极其自信且从容淡定的说了一通 ...

  3. 图解Redis事务机制

    来自:Java中文社群 作为关系型数据库中一项非常重要的基础功能--事务,在 Redis 中是如何处理并使用的? 1.前言 事务指的是提供一种将多个命令打包,一次性按顺序地执行的机制,并且保证服务器只 ...

  4. 【带你重拾Redis】Redis事务

    Redis事务 Redis的事务主要依赖于WATCH ,UNWATCH,MULTI , EXEC, DISCARD等命令. 其中 MULTI , EXEC , DISCARD 分别对应关系型数据库的 ...

  5. redis中有key但是删不掉_一篇图文,搞定Redis事务

    在面试的时候,通常会被问到redis是如何实现分布式锁的.如果我们只知道是借助于setnx/setex ,而不了解setnx/setex底层如何实现,那基本上是无法过关的,尤其是大厂.那究竟redis ...

  6. Redis事务和watch

    redis的事务 严格意义来讲,redis的事务和我们理解的传统数据库(如mysql)的事务是不一样的. redis中的事务定义 Redis中的事务(transaction)是一组命令的集合. 事务同 ...

  7. 十七、Redis事务

    一.Redis事务的概念: Redis 事务的本质是一组命令的集合.事务支持一次执行多个命令,一个事务中所有命令都会被序列化.在事务执行过程,会按照顺序串行化执行队列中的命令,其他客户端提交的命令请求 ...

  8. Redis - 事务

    一.概述 和传统关系型数据库一样,Redis 同样是支持事务的.Redis 的事务可以通过 MULTI/EXEC/DISCARD/WATCH 等命令来实现. 二.事务的 ACID 特性 1). 原子性 ...

  9. python redis事务_python redis事务源码及应用分析

    在多个客户端同时处理相同的数据时,不谨慎的操作很容易导致数据出错.一般的关系型数据库中有事务保证了数据操作的原子性,同样Redis中也设置了事务,可以理解为"将多个命令打包,然后一次性.按顺 ...

  10. Redis事务(transaction)

    Redis事务(transaction) 本文档翻译自: http://redis.io/topics/transactions . MULTI . EXEC . DISCARD 和 WATCH 是 ...

最新文章

  1. 查询出来时间不对_2020年一级、二级建造师执业资格考试成绩可查询!
  2. iframe子页面点击按钮,执行父页面的点击事件
  3. 【leetcode】Single Number (Medium) ☆
  4. 云原生存储项目ROOK
  5. java swing运行没反应_java – 无法从命令行运行swing
  6. 运维管理工具-- Deploy Assistant
  7. (33)SystemVerilog语言编写二分频
  8. 《DSP using MATLAB》示例Example4.2
  9. C++进阶教程之文件和流
  10. phpserialize ,PHP 中变量序列化和反序列化在 Python 中的实现
  11. jQuery将json对象转为字符串,将json字符串转为对象
  12. 教大家一种迅雷下载百度云文件方法
  13. 发改委交通运输部印发《推进“互联网+”便捷交通 促进智能交通发展的实施方案》(全文)...
  14. iec61508最新2020_功能安全IEC61508标准新旧版的对比
  15. 再谈自适应学习——技术篇
  16. 亚马逊账号关联因素有哪些?如何避免账号之间关联
  17. 产品思维 -- 用户体验
  18. Unity零基础到进阶 ☀️| UGUI布局 之Content Size Fitter组件介绍 和 使用示例
  19. 绩点 计算 html,GPA算法大全,教你怎么算GPA
  20. conenct的用法(2种方法)

热门文章

  1. 【框架】Servlet API
  2. mysql 数字用什么类型_MySQL数字类型中的三种常用种类
  3. mac终端 install_2020,搞个 Mac 玩玩!
  4. three.js去掉背景颜色
  5. CSS - 响应式布局(一)媒体查询
  6. 深度学习环境 Ubuntu16.04 LTS + GTX750Ti + CUDA9.0 + cudnn7.0 + python3.6.5 + tensorflow1.6搭建
  7. python显示图片_python图像显示
  8. 基于SSM框架的连锁超市购物零售后台管理系统
  9. python人像变卡通,Python实现将照片变成卡通图片的方法【基于opencv】
  10. Cuda实战-03 显卡设备信息