Redis教程(八):事务详解

张小碗 分享 时间: 收藏本文

【简介】感谢网友“张小碗”参与投稿,以下是小编为大家整理的Redis教程(八):事务详解(共8篇),仅供参考,欢迎大家阅读。

篇1:Redis教程(八):事务详解

这篇文章主要介绍了Redis教程(八):事务详解,本文讲解了,本文讲解了事务概述、相关命令列表、命令使用示例、WATCH命令和基于CAS的乐观锁等内容,需要的朋友可以参考下

一、概述:

和众多其它数据库一样,Redis作为NoSQL数据库也同样提供了事务机制,在Redis中,MULTI/EXEC/DISCARD/WATCH这四个命令是我们实现事务的基石。相信对有关系型数据库开发经验的开发者而言这一概念并不陌生,即便如此,我们还是会简要的列出Redis中事务的实现特征:

1). 在事务中的所有命令都将会被串行化的顺序执行,事务执行期间,Redis不会再为其它客户端的请求提供任何服务,从而保证了事物中的所有命令被原子的执行。

2). 和关系型数据库中的事务相比,在Redis事务中如果有某一条命令执行失败,其后的命令仍然会被继续执行。

3). 我们可以通过MULTI命令开启一个事务,有关系型数据库开发经验的人可以将其理解为“BEGIN TRANSACTION”语句。在该语句之后执行的命令都将被视为事务之内的操作,最后我们可以通过执行EXEC/DISCARD命令来提交/回滚该事务内的所有操作。这两个Redis命令可被视为等同于关系型数据库中的COMMIT/ROLLBACK语句。

4). 在事务开启之前,如果客户端与服务器之间出现通讯故障并导致网络断开,其后所有待执行的语句都将不会被服务器执行。然而如果网络中断事件是发生在客户端执行EXEC命令之后,那么该事务中的所有命令都会被服务器执行。

5). 当使用Append-Only模式时,Redis会通过调用系统函数write将该事务内的所有写操作在本次调用中全部写入磁盘。然而如果在写入的过程中出现系统崩溃,如电源故障导致的宕机,那么此时也许只有部分数据被写入到磁盘,而另外一部分数据却已经丢失。Redis服务器会在重新启动时执行一系列必要的一致性检测,一旦发现类似问题,就会立即退出并给出相应的错误提示。此时,我们就要充分利用Redis工具包中提供的redis-check-aof工具,该工具可以帮助我们定位到数据不一致的错误,并将已经写入的部分数据进行回滚。修复之后我们就可以再次重新启动Redis服务器了。

二、相关命令列表:

命令原型时间复杂度命令描述返回值MULTI用于标记事务的开始,其后执行的命令都将被存入命令队列,直到执行EXEC时,这些命令才会被原子的执行。始终返回OKEXEC执行在一个事务内命令队列中的所有命令,同时将当前连接的状态恢复为正常状态,即非事务状态。如果在事务中执行了WATCH命令,那么只有当WATCH所监控的Keys没有被修改的前提下,EXEC命令才能执行事务队列中的所有命令,否则EXEC将放弃当前事务中的所有命令。原子性的返回事务中各条命令的返回结果。如果在事务中使用了WATCH,一旦事务被放弃,EXEC将返回NULL-multi-bulk回复。DISCARD回滚事务队列中的所有命令,同时再将当前连接的状态恢复为正常状态,即非事务状态。如果WATCH命令被使用,该命令将UNWATCH所有的Keys。始终返回OK。WATCHkey [key ...]O(1)在MULTI命令执行之前,可以指定待监控的Keys,然而在执行EXEC之前,如果被监控的Keys发生修改,EXEC将放弃执行该事务队列中的所有命令。始终返回OK。UNWATCHO(1)取消当前事务中指定监控的Keys,如果执行了EXEC或DISCARD命令,则无需再手工执行该命令了,因为在此之后,事务中所有被监控的Keys都将自动取消。始终返回OK。

三、命令示例:

1. 事务被正常执行:

代码如下:

#在Shell命令行下执行Redis的客户端工具。

/>redis-cli

#在当前连接上启动一个新的事务。

redis 127.0.0.1:6379>multi

OK

#执行事务中的第一条命令,从该命令的返回结果可以看出,该命令并没有立即执行,而是存于事务的命令队列。

redis 127.0.0.1:6379>incr t1

QUEUED

#又执行一个新的命令,从结果可以看出,该命令也被存于事务的命令队列。

redis 127.0.0.1:6379>incr t2

QUEUED

#执行事务命令队列中的所有命令,从结果可以看出,队列中命令的结果得到返回。

redis 127.0.0.1:6379>exec

1) (integer) 1

2) (integer) 1

2. 事务中存在失败的命令:

代码如下:

#开启一个新的事务,

redis 127.0.0.1:6379>multi

OK

#设置键a的值为string类型的3。

redis 127.0.0.1:6379>set a 3

QUEUED

#从键a所关联的值的头部弹出元素,由于该值是字符串类型,而lpop命令仅能用于List类型,因此在执行exec命令时,该命令将会失败。

redis 127.0.0.1:6379>lpop a

QUEUED

#再次设置键a的值为字符串4。

redis 127.0.0.1:6379>set a 4

QUEUED

#获取键a的值,以便确认该值是否被事务中的第二个set命令设置成功。

redis 127.0.0.1:6379>get a

QUEUED

#从结果中可以看出,事务中的第二条命令lpop执行失败,而其后的set和get命令均执行成功,这一点是Redis的事务与关系型数据库中的事务之间最为重要的差别。

redis 127.0.0.1:6379>exec

1) OK

2) (error) ERR Operation against a key holding the wrong kind of value

3) OK

4) “4”

3. 回滚事务:

代码如下:

#为键t2设置一个事务执行前的值。

redis 127.0.0.1:6379>set t2 tt

OK

#开启一个事务。

redis 127.0.0.1:6379>multi

OK

#在事务内为该键设置一个新值。

redis 127.0.0.1:6379>set t2 ttnew

QUEUED

#放弃事务。

redis 127.0.0.1:6379>discard

OK

#查看键t2的值,从结果中可以看出该键的值仍为事务开始之前的值。

redis 127.0.0.1:6379>get t2

“tt”

四、WATCH命令和基于CAS的乐观锁:

在Redis的事务中,WATCH命令可用于提供CAS(check-and-set)功能。假设我们通过WATCH命令在事务执行之前监控了多个Keys,倘若在WATCH之后有任何Key的值发生了变化,EXEC命令执行的事务都将被放弃,同时返回Null multi-bulk应答以通知调用者事务执行失败。例如,我们再次假设Redis中并未提供incr命令来完成键值的原子性递增,如果要实现该功能,我们只能自行编写相应的代码。其伪码如下:

代码如下:

val = GET mykey

val = val + 1

SET mykey $val

以上代码只有在单连接的情况下才可以保证执行结果是正确的,因为如果在同一时刻有多个客户端在同时执行该段代码,那么就会出现多线程程序中经常出现的一种错误场景--竞态争用(race condition)。比如,客户端A和B都在同一时刻读取了mykey的原有值,假设该值为10,此后两个客户端又均将该值加一后set回Redis服务器,这样就会导致mykey的结果为11,而不是我们认为的12。为了解决类似的问题,我们需要借助WATCH命令的帮助,见如下代码:

代码如下:

WATCH mykey

val = GET mykey

val = val + 1

MULTI

SET mykey $val

EXEC

和此前代码不同的是,新代码在获取mykey的值之前先通过WATCH命令监控了该键,此后又将set命令包围在事务中,这样就可以有效的保证每个连接在执行EXEC之前,如果当前连接获取的mykey的值被其它连接的客户端修改,那么当前连接的EXEC命令将执行失败。这样调用者在判断返回值后就可以获悉val是否被重新设置成功。

篇2:Redis学习手册(事务)

#在Shell命令行下执行Redis的客户端工具。 />redis-cli #在当前连接上启动一个新的事务。 redis 127.0.0.1:6379>multi OK #执行事务中的第一条命令,从该命令的返回结果可以看出,该命令并没有立即执行,而是存于事务的命令队列。 redis 127.0.0.1:6379>incr t1 QUEUED #又执行一个新的命令,从结果可以看出,该命令也被存于事务的命令队列。 redis 127.0.0.1:6379>incr t2 QUEUED #执行事务命令队列中的所有命令,从结果可以看出,队列中命令的结果得到返回。 redis 127.0.0.1:6379>exec 1) (integer) 1 2) (integer) 1

篇3:Redis学习手册(事务)

#为键t2设置一个事务执行前的值。 redis 127.0.0.1:6379>set t2 tt OK #开启一个事务。 redis 127.0.0.1:6379>multi OK #在事务内为该键设置一个新值。 redis 127.0.0.1:6379>set t2 ttnew QUEUED #放弃事务。 redis 127.0.0.1:6379>discard OK #查看键t2的值,从结果中可以看出该键的值仍为事务开始之前的值。 redis 127.0.0.1:6379>get t2 “tt”

篇4:Redis学习手册(事务)

#开启一个新的事务。 redis 127.0.0.1:6379>multi OK #设置键a的值为string类型的3。 redis 127.0.0.1:6379>set a 3 QUEUED #从键a所关联的值的头部弹出元素,由于该值是字符串类型,而lpop命令仅能用于List类型,因此在执行exec命令时,该命令将会失败。 redis 127.0.0.1:6379>lpop a QUEUED #再次设置键a的值为字符串4。 redis 127.0.0.1:6379>set a 4 QUEUED #获取键a的值,以便确认该值是否被事务中的第二个set命令设置成功。 redis 127.0.0.1:6379>get a QUEUED #从结果中可以看出,事务中的第二条命令lpop执行失败,而其后的set和get命令均执行成功,这一点是Redis的事务与关系型数据库中的事务之间最为重要的差别。 redis 127.0.0.1:6379>exec 1) OK 2) (error) ERR Operation against a key holding the wrong kind of value 3) OK 4) “4”

篇5:Redis教程(七):Key操作命令详解

这篇文章主要介绍了Redis教程(七):Key操作命令详解,本文讲解了Key操作命令概述、相关命令列表、命令使用示例等内容,需要的朋友可以参考下

一、概述:

在该系列的前几篇博客中,主要讲述的是与Redis数据类型相关的命令,如String、List、Set、Hashes和Sorted-Set,这些命令都具有一个共同点,即所有的操作都是针对与Key关联的Value的。而该篇博客将主要讲述与Key相关的Redis命令。学习这些命令对于学习Redis是非常重要的基础,也是能够充分挖掘Redis潜力的利器。

在该篇博客中,我们将一如既往的给出所有相关命令的明细列表和典型示例,以便于我们现在的学习和今后的查阅。

二、相关命令列表:

命令原型时间复杂度命令描述返回值KEYSpatternO(N)时间复杂度中的N表示数据库中Key的数量。获取所有匹配pattern参数的Keys。需要说明的是,在我们的正常操作中应该尽量避免对该命令的调用,因为对于大型数据库而言,该命令是非常耗时的,对Redis服务器的性能打击也是比较大的。pattern支持glob-style的通配符格式,如*表示任意一个或多个字符,?表示任意字符,[abc]表示方括号中任意一个字母。匹配模式的键列表。DELkey [key ...]O(N)时间复杂度中的N表示删除的Key数量。从数据库删除中参数中指定的keys,如果指定键不存在,则直接忽略。还需要另行指出的是,如果指定的Key关联的数据类型不是String类型,而是List、Set、Hashes和Sorted Set等容器类型,该命令删除每个键的时间复杂度为O(M),其中M表示容器中元素的数量。而对于String类型的Key,其时间复杂度为O(1)。实际被删除的Key数量。EXISTSkeyO(1)判断指定键是否存在。1表示存在,0表示不存在。MOVEkey dbO(1)将当前数据库中指定的键Key移动到参数中指定的数据库中。如果该Key在目标数据库中已经存在,或者在当前数据库中并不存在,该命令将不做任何操作并返回0。移动成功返回1,否则0。RENAMEkey newkeyO(1)为指定指定的键重新命名,如果参数中的两个Keys的命令相同,或者是源Key不存在,该命令都会返回相关的错误信息。如果newKey已经存在,则直接覆盖。RENAMENXkey newkeyO(1)如果新值不存在,则将参数中的原值修改为新值。其它条件和RENAME一致。1表示修改成功,否则0。PERSISTkeyO(1)如果Key存在过期时间,该命令会将其过期时间消除,使该Key不再有超时,而是可以持久化存储。1表示Key的过期时间被移出,0表示该Key不存在或没有过期时间。EXPIREkey secondsO(1)该命令为参数中指定的Key设定超时的秒数,在超过该时间后,Key被自动的删除。如果该Key在超时之前被修改,与该键关联的超时将被移除。1表示超时被设置,0则表示Key不存在,或不能被设置。EXPIREATkey timestampO(1)该命令的逻辑功能和EXPIRE完全相同,唯一的差别是该命令指定的超时时间是绝对时间,而不是相对时间。该时间参数是Unix timestamp格式的,即从1970年1月1日开始所流经的秒数。1表示超时被设置,0则表示Key不存在,或不能被设置。TTLkeyO(1)获取该键所剩的超时描述。返回所剩描述,如果该键不存在或没有超时设置,则返回-1。RANDOMKEYO(1)从当前打开的数据库中随机的返回一个Key。返回的随机键,如果该数据库是空的则返回nil。TYPEkeyO(1)获取与参数中指定键关联值的类型,该命令将以字符串的格式返回。返回的字符串为string、list、set、hash和zset,如果key不存在返回none。SORTkey [BY pattern] [LIMIT offset count] [GET pattern [GET pattern ...]] [ASC|DESC] [ALPHA] [STORE destination]O(N+M*log(M))这个命令相对来说是比较复杂的,因此我们这里只是给出最基本的用法,有兴趣的网友可以去参考redis的官方文档。返回排序后的原始列表。

三、命令示例:

1. KEYS/RENAME/DEL/EXISTS/MOVE/RENAMENX:

代码如下:

#在Shell命令行下启动Redis客户端工具。

/>redis-cli

#清空当前选择的数据库,以便于对后面示例的理解。

redis 127.0.0.1:6379>flushdb

OK

#添加String类型的模拟数据。

redis 127.0.0.1:6379>set mykey 2

OK

redis 127.0.0.1:6379>set mykey2 “hello”

OK

#添加Set类型的模拟数据。

redis 127.0.0.1:6379>sadd mysetkey 1 2 3

(integer) 3

#添加Hash类型的模拟数据。

redis 127.0.0.1:6379>hset mmtest username “stephen”

(integer) 1

#根据参数中的模式,获取当前数据库中符合该模式的所有key,从输出可以看出,该命令在执行时并不区分与Key关联的Value类型。

redis 127.0.0.1:6379>keys my*

1) “mysetkey”

2) “mykey”

3) “mykey2”

#删除了两个Keys。

redis 127.0.0.1:6379>del mykey mykey2

(integer) 2

#查看一下刚刚删除的Key是否还存在,从返回结果看,mykey确实已经删除了。

redis 127.0.0.1:6379>exists mykey

(integer) 0

#查看一下没有删除的Key,以和上面的命令结果进行比较。

redis 127.0.0.1:6379>exists mysetkey

(integer) 1

#将当前数据库中的mysetkey键移入到ID为1的数据库中,从结果可以看出已经移动成功。

redis 127.0.0.1:6379>move mysetkey 1

(integer) 1

#打开ID为1的数据库。

redis 127.0.0.1:6379>select 1

OK

#查看一下刚刚移动过来的Key是否存在,从返回结果看已经存在了。

redis 127.0.0.1:6379[1]>exists mysetkey

(integer) 1

#在重新打开ID为0的缺省数据库,

redis 127.0.0.1:6379[1]>select 0

OK

#查看一下刚刚移走的Key是否已经不存在,从返回结果看已经移走。

redis 127.0.0.1:6379>exists mysetkey

(integer) 0

#准备新的测试数据。

redis 127.0.0.1:6379>set mykey “hello”

OK

#将mykey改名为mykey1

redis 127.0.0.1:6379>rename mykey mykey1

OK

#由于mykey已经被重新命名,再次获取将返回nil。

redis 127.0.0.1:6379>get mykey

(nil)

#通过新的键名获取。

redis 127.0.0.1:6379>get mykey1

“hello”

#由于mykey已经不存在了,所以返回错误信息。

redis 127.0.0.1:6379>rename mykey mykey1

(error) ERR no such key

#为renamenx准备测试key

redis 127.0.0.1:6379>set oldkey “hello”

OK

redis 127.0.0.1:6379>set newkey “world”

OK

#由于newkey已经存在,因此该命令未能成功执行。

redis 127.0.0.1:6379>renamenx oldkey newkey

(integer) 0

#查看newkey的值,发现它也没有被renamenx覆盖。

redis 127.0.0.1:6379>get newkey

“world”

2. PERSIST/EXPIRE/EXPIREAT/TTL:

代码如下:

#为后面的示例准备的测试数据。

redis 127.0.0.1:6379>set mykey “hello”

OK

#将该键的超时设置为100秒。

redis 127.0.0.1:6379>expire mykey 100

(integer) 1

#通过ttl命令查看一下还剩下多少秒。

redis 127.0.0.1:6379>ttl mykey

(integer) 97

#立刻执行persist命令,该存在超时的键变成持久化的键,即将该Key的超时去掉。

redis 127.0.0.1:6379>persist mykey

(integer) 1

#ttl的返回值告诉我们,该键已经没有超时了。

redis 127.0.0.1:6379>ttl mykey

(integer) -1

#为后面的expire命令准备数据。

redis 127.0.0.1:6379>del mykey

(integer) 1

redis 127.0.0.1:6379>set mykey “hello”

OK

#设置该键的超时被100秒。

redis 127.0.0.1:6379>expire mykey 100

(integer) 1

#用ttl命令看一下当前还剩下多少秒,从结果中可以看出还剩下96秒。

redis 127.0.0.1:6379>ttl mykey

(integer) 96

#重新更新该键的超时时间为20秒,从返回值可以看出该命令执行成功。

redis 127.0.0.1:6379>expire mykey 20

(integer) 1

#再用ttl确认一下,从结果中可以看出果然被更新了。

redis 127.0.0.1:6379>ttl mykey

(integer) 17

#立刻更新该键的值,以使其超时无效。

redis 127.0.0.1:6379>set mykey “world”

OK

#从ttl的结果可以看出,在上一条修改该键的命令执行后,该键的超时也无效了。

redis 127.0.0.1:6379>ttl mykey

(integer) -1

3. TYPE/RANDOMKEY/SORT:

代码如下:

#由于mm键在数据库中不存在,因此该命令返回none。

redis 127.0.0.1:6379>type mm

none

#mykey的值是字符串类型,因此返回string。

redis 127.0.0.1:6379>type mykey

string

#准备一个值是set类型的键。

redis 127.0.0.1:6379>sadd mysetkey 1 2

(integer) 2

#mysetkey的键是set,因此返回字符串set。

redis 127.0.0.1:6379>type mysetkey

set

#返回数据库中的任意键。

redis 127.0.0.1:6379>randomkey

“oldkey”

#清空当前打开的数据库。

redis 127.0.0.1:6379>flushdb

OK

#由于没有数据了,因此返回nil。

redis 127.0.0.1:6379>randomkey

(nil)

篇6:Redis命令详解关键字

整数:被删除的keys的数量

2.dump key

加入版本 2.6.0。

时间复杂度: 查找给定键的复杂度为 O(1) ,对键进行序列化的复杂度为 O(N*M) ,其中 N 是构成 key 的 Redis 对象的数量,而 M 则是这些对象的平均大小。如果序列化的对象是比较小的字符串,那么复杂度为 O(1) 。

序列化给定 key ,并返回被序列化的值,使用 RESTORE 命令可以将这个值反序列化为 Redis 键。

序列化生成的值有以下几个特点:

它带有 64 位的校验和,用于检测错误,RESTORE 在进行反序列化之前会先检查校验和。

值的编码格式和 RDB 文件保持一致。

RDB 版本会被编码在序列化值当中,如果因为 Redis 的版本不同造成 RDB 格式不兼容,那么 Redis 会拒绝对这个值进行反序列化操作。

序列化的值不包括任何生存时间信息。

篇7:Redis命令详解关键字

如果 key 不存在,那么返回 nil。

否则,返回序列化之后的值。

3.restore key ttl serialized-value

加入版本 2.6.0。

时间复杂度: 查找给定键的复杂度为 O(1) ,对键进行反序列化的复杂度为 O(N*M) ,其中 N 是构成 key 的 Redis 对象的数量,而 M 则是这些对象的平均大小。 有序集合(sorted set)的反序列化复杂度为 O(N*M*log(N)) ,因为有序集合每次插入的复杂度为 O(log(N)) ,

如果反序列化的对象是比较小的字符串,那么复杂度为 O(1) 。

反序列化给定的序列化值,并将它和给定的 key 关联。

参数 ttl 以毫秒为单位为 key 设置生存时间;如果 ttl 为 0 ,那么不设置生存时间。

RESTORE 在执行反序列化之前会先对序列化值的 RDB 版本和数据校验和进行检查,如果 RDB 版本不相同或者数据不完整的话,那么 RESTORE 会拒绝进行反序列化,并返回一个错误。

篇8:Redis命令详解关键字

整数,如下的整数结果

1 如果key存在

0 如果key不存在

5.expire key seconds

加入版本 1.0.0。

时间复杂度: O(1)。

设置key的过期时间。如果key已过期,将会被自动删除。设置了过期时间的key被称之为volatile。

在key过期之前可以重新更新他的过期时间,也可以使用PERSIST命令删除key的过期时间。

在Redis< 2.1.3之前的版本,key的生存时间可以被更新

Note that in Redis 2.4 the expire might not be pin-point accurate, and it could be between zero to one seconds out. Development versions of Redis fixed this bug and Redis 2.6 will feature a millisecond precision EXPIRE.

相关专题 详解事务