更多技术文章,请关注我的个人博客 www.immaxfang.com 和小公众号 Max的学习札记

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事务机制与Lind.DDD.Repositories.Redis事务机制的实现

    回到目录 Redis本身支持事务,这就是SQL数据库有Transaction一样,而Redis的驱动也支持事务,这在ServiceStack.Redis就有所体现,它也是目前最受业界认可的Redis ...

  2. Redis学习笔记(4) Redis事务、生存时间及排序

    1. Redis事务 Redis中的事务(transaction)是一组命令的集合,一个事务中的命令要么都执行,要么都不执行.事务的原理是先将属于一个事务的命令发送给Redis,然后再让Redis依次 ...

  3. REDIS 事务机制

    基本事务操作: 任何数据库都必须要保证一种原子执行操作:最基本的原子执行操作肯定是需要提供: 举一个例子来说明: 当对某个Key 做一个统计: 可能不同的Client做它那部分的统计,一段时间后,服务 ...

  4. redis 事务

    概述 相信学过MySQL等其他数据库的同学对事务这个词都不陌生,事务表示的是一组动作,这组动作要么全部执行,要么全部不执行.为什么会有这样的需求呢?看看下面的场景: 微博是一个弱关系型社交网络,用户之 ...

  5. (6)redis 事务

    redis对事务的支持目前还比较简单.redis只能保证一个client发起的事务中的命令可以连续的执行,而中间不会插入其他client的命令. 由于redis是单线程来处理所有client的请求的所 ...

  6. Redis事务的分析及改进

    Redis事务的分析及改进 Redis的事务特性 数据ACID特性满足了几条? 为了保持简单,redis事务保证了其中的一致性和隔离性: 不满足原子性和持久性: 原子性 redis事务在执行的中途遇到 ...

  7. Spring Framework 中启动 Redis 事务操作

    背景: 项目中遇到有一系列对Redis的操作,并需要保持事务处理. 环境: Spring version 4.1.8.RELEASE Redis Server 2.6.12 (64位) spring- ...

  8. 2016022612 - redis事务命令集合

    参考地址:http://www.yiibai.com/redis/redis_transactions.html Redis事务由指令 MULTI 启动,以EXEC结束. 1.multi 用途:事务开 ...

  9. 2016022606 - redis事务

    Redis事务 Redis事务让一组命令在单个步骤执行.事务中有两个属性,说明如下: 1.在一个事务中的所有命令按顺序执行作为单个隔离操作.通过另一个客户端发出的请求在Redis的事务的过程中执行,这 ...

  10. Redis事务和分布式锁

    Redis事务 Redis中的事务(transaction)是一组命令的集合.事务同命令一样都是Redis最小的执行单位,一个事务中的命令要么都执行,要么都不执行.Redis事务的实现需要用到 MUL ...

随机推荐

  1. Luogu2858[USACO06FEB]奶牛零食Treats for the Cows (区间DP)

    我是个傻逼,这么水的题都会T #include <iostream> #include <cstdio> #include <cstring> #include & ...

  2. Canvas 笔记目录

    Canvas 基础笔记 初次认识 Canvas Canvas 线性图形(一):路径 Canvas 线性图形(二):圆形 Canvas 线性图形(三):曲线 Canvas 线性图形(四):矩形 Canv ...

  3. iommu系列之---概念解释篇

    本文会对iommu中的一些容易引起疑惑的概念进行阐述,内核版本为4.19. 先上简写: DMAR - DMA remapping DRHD - DMA Remapping Hardware Unit ...

  4. PHP一句话简单免杀

    PHP一句话简单免杀 原型 几种已经开源的免杀思路 拆解合并 <?php $ch = explode(".","hello.ev.world.a.l"); ...

  5. 在Boss直聘上投简历时,怎样保证有新消息时能及时收到

    最近在Boss直聘上投简历,偶尔会有HR给我发消息,不想在电脑上错过这些消息,但我又不能时时刻刻盯着这个页,怎么办呢? 这时,我想起来,之前做过的Chrome插件,如果检测到Boss直聘上新消息数大于 ...

  6. 端口安全 | DHCP snooping

    1.端口安全用于防止mac地址的欺骗.mac地址泛洪攻击.主要思想就是在交换机的端口下通过手工或者自动绑定mac地址,这就就只能是绑定的mac地址能够通过. 2.通过静态的端口绑定:将mac地址手工静 ...

  7. Linux系统启动报错No bootable device解决步骤

    CSDN文章地址点击此处 磁盘的 MBR 表损坏 实验环境准备工作 查看分区类型及磁盘位置信息点击此篇 首先备份虚拟机A上的 MBR 表 dd if=/dev/vda of=MBR bs=512 co ...

  8. mysql_唯一索引数据重复问题总结

    CREATE TABLE `tt_transfer_assemble_diffuse_plan_info` ( `id` bigint(20) unsigned NOT NULL AUTO_INCRE ...

  9. [Python]-string-字符串

    字符串是Python中很常用的数据类型,此处记录一些典型用法并随时更新. split()方法 通过指定分隔符对字符串进行切片,如果参数 num 有指定值,则分隔 num+1 个子字符串. 两个参数st ...

  10. 对于Java中权限修饰符的理解

    老是把Java中权限修饰符给忘记,写一个博客加深印象吧 权限分为四个作用域:当前类,同一个包,其他包的子类,其他包的类. 首先要知道包的概念,Java中一个包是指一个package下的所有文件. pr ...