(四)Redis的事务

什么是事务

  在讲解Redis事务以前,先复习一下事务的定义(来源于维基百科):

  数据库事务(简称:事务)是数据库管理系统执行过程中的一个逻辑单位,由一个有限的数据库操作序列构成。

数据库事务通常包含了一个序列的对数据库的读/写操作。包含有以下两个目的:

  1. 为数据库操作序列提供了一个从失败中恢复到正常状态的方法,同时提供了数据库即使在异常状态下仍能保持一致性的方法。
  2. 当多个应用程序并发访问数据库时,可以在这些应用程序之间提供一个隔离方法,以防止彼此的操作互相干扰。

  当事务被提交给了数据库管理系统(DBMS),则DBMS需要确保该事务中的所有操作都成功完成且其结果被永久保存在数据库中,如果事务中有的操作没有成功完成,则事务中的所有操作都需要回滚,回到事务执行前的状态;同时,该事务对数据库或者其他事务的执行无影响,所有的事务都好像在独立的运行。

Redis事务

  Redis通过multiexexwatch等命令来实现事务功能。事务提供了一种将多个命令请求打包,然后一次性、按书讯地执行多个命令的机制,并且在事务执行期间,服务器不会中断事务而改去执行其他客户端的命令请求,它会将事务中的所有命令都执行完毕,然后才去处理其他客户端的命令请求。
  以下是一个事务的例子, 它先以 MULTI 命令开始一个事务, 之后输入的命令都会依次进入命令队列,但不会执行, 直到输入EXEC 命令触发事务, 才一并依次执行事务中的所有命令(组队的过程中可以用DISCARD来放弃组队取消事务):

127.0.0.1:6379> multi
OK
127.0.0.1:6379> set book-name "Thinking in Java"
QUEUED
127.0.0.1:6379> get book-name
QUEUED
127.0.0.1:6379> sadd user jack tom lucy
QUEUED
127.0.0.1:6379> smembers user
QUEUED
127.0.0.1:6379> exec
1) OK
2) "Thinking in Java"
3) (integer) 3
4) 1) "jack"
   2) "lucy"
   3) "tom"

  事务命令说明如下:

  • multi:标记一个事务块的开始
  • exec:执行所有事务块内的命令
  • discard:取消事务,放弃执行事务块内的所有命令
  • watch <key1> <key2>...:监视一个(或多个) key ,如果在事务执行之前这个(或这些) key 被其他命令所改动,那么事务将被打断
  • unwatch:取消watch命令对所有 key 的监视(如果执行watch命令后execdiscard命令先被执行,则unwatch自动被执行)

事务的实现

  官网的事务解释:Redis 事务可以一次执行多个命令, 并且带有以下两个特性:
事务是一个单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。
事务是一个原子操作:事务中的命令要么全部被执行,要么全部都不执行。
  但是第二个特性有争论,很多人说Redis事务不保证原子性:虽然Redis的单个命令是原子性的,但同一个事务中如果有一条命令执行失败,其后的命令还是会执行,没有回滚

  再补充一个特性:没有隔离级别的概念:队列中的命令没有提交之前都不会实际的被执行,因为事务提交前任何指令都不会被实际执行,也就不存在“事务内的查询要看到事务里的更新,在事务外查询不能看到”这种问题

一个事务从开始到执行会经历以下三个阶段:

  • 开始事务。
  • 命令入队。
  • 执行事务。

事务的开始

  multi命令的执行标识着事务的开始(即将执行该命令的客户端从非事务状态切换至事务状态):

127.0.0.1:6380> multi
OK

命令入队

  当一个客户端处于非事务状态时,其命令会被服务器立即执行:

127.0.0.1:6380> set age 18
OK

  与此不同,当我们使用multi命令开启事务,切换到事务状态后,服务器会根据客户端发来的不同命令执行不同的操作,具体流程如下图:

服务器判断命令是否该入队还是执行

  每个Redis客户端都有自己的事务状态,其被保存在客户端状态的master属性里面(了解),事务状态包含一个事务队列,以及一个已入队命令的计数器(可以说是事务队列的长度)。事务队列是一个数组,以先进先出(FIFO)的方式保存了相关已入队命令的信息。例如如果我们执行下面的命令:

127.0.0.1:6379> multi
OK
127.0.0.1:6379> set book-name "Thinking in Java"
QUEUED
127.0.0.1:6379> get book-name
QUEUED
127.0.0.1:6379> sadd user jack tom lucy
QUEUED
127.0.0.1:6379> smembers user
QUEUED
  • 则最先入队的set命令被放在事务队列的索引0位置上;
  • 第二入队的get命令被放在事务队列的索引1位置上;
  • 第三入队的sadd命令被放在事务队列的索引2位置上;
  • 最后入队的smembers命令被放在事务队列的索引3位置上;

执行事务

  当一个处于事务状态的客户端向服务器发送exec命令时,这个exec命令立即被服务器执行。服务器会便利这个客户端的事务队列,执行其中保存的所有命令,最后将执行命令所得的结果全部返回给客户端,对上面的例子执行exec命令则返回:

127.0.0.1:6379> exec
1) OK
2) "Thinking in Java"
3) (integer) 3
4) 1) "jack"
   2) "lucy"
   3) "tom"

watch命令介绍

127.0.0.1:6379> set age 16
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> incr age
QUEUED
127.0.0.1:6379> incr age
QUEUED
127.0.0.1:6379> exec
1) (integer) 18
2) (integer) 19

  当我们为一个键开启事务时,若想要对键age执行增量操作,执行2次incr age命令后age的值应为18(此时未exec),但是如果在这个过程中另一个客户端也执行了增量操作,最后的结果就是19,这显然不是我们所希望看到的。为了解决这种问题,我们可以使用watch命令:

127.0.0.1:6379> set age 16
OK
127.0.0.1:6379> watch age
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> incr age
QUEUED
127.0.0.1:6379> incr age
QUEUED
127.0.0.1:6379> exec
1) (integer) 17
2) (integer) 18

事务的ACID性质

  在传统的关系型数据库中,常常用ACID性质来检验事务功能的可靠性和安全性。
  在Redis中,事务总是具有原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation),并且当Redis运行在某种特定的持久化模式下时,事务也具有持久性(Durability)。

原子性

  事务具有原子性是指,数据库将事务中的多个操作当作一个整体来执行,服务器要么就执行事务中的所有操作,要么就一个操作也不执行。
  对于Redis来说,事务队列中的命令要么就全部都执行,要么就一个都不执行,因此,Redis事务是具有原子性的。(有争论,其实不能完全保证原子性,等等讨论)
  举个例子,以下是一个成功执行的事务:

127.0.0.1:6380> multi
OK
127.0.0.1:6380> set msg "hello world"
QUEUED
127.0.0.1:6380> get msg
QUEUED
127.0.0.1:6380> exec
1) OK
2) "hello world"

  再举个执行事务失败的例子,这个事务因为命令入队出错而被服务器拒绝执行,事务中的所有命令都不会被执行:

127.0.0.1:6380> multi
OK
127.0.0.1:6380> set msg "hello world"
QUEUED
127.0.0.1:6380> get
(error) ERR wrong number of arguments for 'get' command
127.0.0.1:6380> get msg
QUEUED
127.0.0.1:6380> exec
(error) EXECABORT Transaction discarded because of previous errors.

  Redis的事务和传统的关系型数据库事务的最大区别在于,Redis不支持事务回滚机制,即使事务队列中的某个命令在执行期间出现语法错误,整个事务也会继续执行下去,直到将事务中的所有命令都执行完毕。
  在下面的例子中,即使set在执行期间出现了语法错误,事务的后续命令也会继续执行下去,并且之后执行的命令也不会有任何影响:

127.0.0.1:6380> multi
OK
127.0.0.1:6380> mset user1 jack user2 luck user3 jim
QUEUED
127.0.0.1:6380> set age 16 18  //语法错误
QUEUED
127.0.0.1:6380> set user4 william
QUEUED
127.0.0.1:6380> exec
1) OK
2) (error) ERR syntax error
3) OK

  Redis的作者在事务功能的文档中解释说,不支持事务回滚是因为这种复杂的功能和Redis追求简单高效的设计主旨不符合,并且它认为,Redis事务的执行时错误通常都是变成错误产生的(确实如此),这种错误只会出现在开发环境中,而很少会在实际的生产环境中出现,所以他认为没有必要为Redis开发事务回滚功能。
  可这是否违反了原子性的定义呢?即要么全部发生,要么全部不发生。注意!!!要么全部不发生就是说明出错时事务可以回滚,可Redis都不支持事务回滚功能,又怎么能支持原子性呢?只能在某种程度上说是原子性吧,即执行事务时正确时是有原子性的,执行失败则是没有原子性的。就像作者说的那样,Redis执行事务就肯定成功的,除非你写错了我规定的格式,不然才会失败,好可爱的作者。。。
  这也解释了为什么可以在很多分析Redis的文章中看到别人说Redis事务是不支持原子性的,确实如此啊!

一致性

  事务的一致性指的是,如果数据库在执行事务之前是一致的,那么在事务执行之后,无论事务是否执行成功,数据库也应该是一致的。
  Redis通过谨慎的错误检测和简单的设计来保证事务的一致性。

隔离性

  事务的隔离性指的是,即使数据库中有多个事务并发地执行,各个事务之间也不会互相影响,并且在并发状态下执行的事务和串行执行的事务产生的结果完全相同。
  可以因为刚好Redis是使用单线程的方式来执行事务(以及事务队列中的命令),并且服务器保证,在执行事务期间不会对事务进行中断,因此,Redis的事务总是以串行的方式运行的,所以其也是具有隔离性的。

持久性

  事务的持久性指的是,当一个事务执行完毕时,执行这个事务所得的结果已经被保存到永久性存储介质(如硬盘)里面了,即使服务器在事务执行完毕之后停机,执行事务所得的结果也不会丢失。
  因为Redis的事务不过是简单地用队列包裹了一组Redis命令,并没有为事务提供任何额外的持久化功能,所以Redis事务的持久性由Redis的持久化模式决定,即RDB或AOF模式下,这些模式下才可能具有持久性,还得看这些模式的具体配置情况。

参考资料

《redis设计与实现》(第二版)
redis官方文档

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270

推荐阅读更多精彩内容

  • redis事务 Redis 通过 MULTI 、 DISCARD 、 EXEC 和 WATCH 四个命令来实现事务...
    全能程序猿阅读 2,137评论 0 11
  • Redis 通过 MULTI 、 DISCARD 、 EXEC 和 WATCH 四个命令来实现事务功能, 本章首先...
    binge1024阅读 509评论 0 2
  • 五种数据结构简介 Redis是使用C编写的,内部实现了一个struct结构体redisObject对象,通过结构体...
    彦帧阅读 6,889评论 0 14
  • MULTI、EXEC、DISCARD和WATCH命令是Redis事务功能的基础。Redis事务允许在一次单独的步骤...
    金星show阅读 472评论 0 0
  • 原文来源Redis的事务功能详解MULTI、EXEC、DISCARD和WATCH命令是Redis事务功能的基础。R...
    白纸糊阅读 1,799评论 1 0