iis服务器助手广告广告
返回顶部
首页 > 资讯 > 数据库 >如何深入理解Redis事务
  • 190
分享到

如何深入理解Redis事务

2024-04-02 19:04:59 190人浏览 安东尼
摘要

本篇内容主要讲解“如何深入理解Redis事务”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“如何深入理解Redis事务”吧!Redis可以看成NoSQL类型的数据

本篇内容主要讲解“如何深入理解Redis事务”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“如何深入理解Redis事务”吧!

如何深入理解Redis事务

Redis可以看成NoSQL类型的数据库系统, Redis也提供了事务, 但是和传统的关系型数据库的事务既有相似性, 也存在区别.因为Redis的架构基于操作系统的多路复用的io接口,主处理流程是一个单线程,因此对于一个完整的命令, 其处理都是原子性的, 但是如果需要将多个命令作为一个不可分割的处理序列, 就需要使用事务.

Redis事务有如下一些特点:

  •  事务中的命令序列执行的时候是原子性的,也就是说,其不会被其他客户端的命令中断. 这和传统的数据库的事务的属性是类似的.

  •  尽管Redis事务中的命令序列是原子执行的, 但是事务中的命令序列执行可以部分成功,这种情况下,Redis事务不会执行回滚操作. 这和传统关系型数据库的事务是有区别的.

  •  尽管Redis有RDB和AOF两种数据持久化机制, 但是其设计目标是高效率的cache系统. Redis事务只保证将其命令序列中的操作结果提交到内存中,不保证持久化到磁盘文件. 更进一步的, Redis事务和RDB持久化机制没有任何关系, 因为RDB机制是对内存数据结构的全量的快照.由于AOF机制是一种增量持久化,所以事务中的命令序列会提交到AOF的缓存中.但是AOF机制将其缓存写入磁盘文件是由其配置的实现策略决定的,和Redis事务没有关系.

Redis事务API

从宏观上来讲, Redis事务开始后, 会缓存后续的操作命令及其操作数据,当事务提交时,原子性的执行缓存的命令序列.

从版本2.2开始,Redis提供了一种乐观的机制, 配合这种机制,Redis事务提交时, 变成了事务的条件执行. 具体的说,如果乐观锁失败了,事务提交时, 丢弃事务中的命令序列,如果乐观锁成功了, 事务提交时,才会执行其命令序列.当然,也可以不使用乐观锁机制, 在事务提交时, 无条件执行事务的命令序列.

Redis事务涉及到MULTI, EXEC, DISCARD, WATCH和UNWATCH这五个命令:

  •  事务开始的命令是MULTI, 该命令返回OK提示信息. Redis不支持事务嵌套,执行多次MULTI命令和执行一次是相同的效果.嵌套执行MULTI命令时,Redis只是返回错误提示信息.

  •  EXEC是事务的提交命令,事务中的命令序列将被执行(或者不被执行,比如乐观锁失败等).该命令将返回响应数组,其内容对应事务中的命令执行结果.

  •  WATCH命令是开始执行乐观锁,该命令的参数是key(可以有多个), Redis将执行WATCH命令的客户端对象和key进行关联,如果其他客户端修改了这些key,则执行WATCH命令的客户端将被设置乐观锁失败的标志.该命令必须在事务开始前执行,即在执行MULTI命令前执行WATCH命令,否则执行无效,并返回错误提示信息.

  •  UNWATCH命令将取消当前客户端对象的乐观锁key,该客户端对象的事务提交将变成无条件执行.

  •  DISCARD命令将结束事务,并且会丢弃全部的命令序列.

需要注意的是,EXEC命令和DISCARD命令结束事务时,会调用UNWATCH命令,取消该客户端对象上所有的乐观锁key.

无条件提交

如果不使用乐观锁, 则事务为无条件提交.下面是一个事务执行的例子:

multi  +OK  incr key1  +QUEUED  set key2 val2  +QUEUED  exec  *2  :1  +OK

当客户端开始事务后, 后续发送的命令将被Redis缓存起来,Redis向客户端返回响应提示字符串QUEUED.当执行EXEC提交事务时,缓存的命令依次被执行,返回命令序列的执行结果.

事务的错误处理

事务提交命令EXEC有可能会失败, 有三种类型的失败场景:

  •  在事务提交之前,客户端执行的命令缓存失败.比如命令的语法错误(命令参数个数错误, 不支持的命令等等).如果发生这种类型的错误,Redis将向客户端返回包含错误提示信息的响应.

  •  事务提交时,之前缓存的命令有可能执行失败.

  •  由于乐观锁失败,事务提交时,将丢弃之前缓存的所有命令序列.

当发生第一种失败的情况下,客户端在执行事务提交命令EXEC时,将丢弃事务中所有的命令序列.下面是一个例子:

multi  +OK  incr num1 num2  -ERR wrong number of arguments for 'incr' command  set key1 val1  +QUEUED  exec  -EXECABORT Transaction discarded because of previous errors.

命令incr num1 num2并没有缓存成功, 因为incr命令只允许有一个参数,是个语法错误的命令.Redis无法成功缓存该命令,向客户端发送错误提示响应.接下来的set key1 val1命令缓存成功.最后执行事务提交的时候,因为发生过命令缓存失败,所以事务中的所有命令序列被丢弃.

如果事务中的所有命令序列都缓存成功,在提交事务的时候,缓存的命令中仍可能执行失败.但Redis不会对事务做任何回滚补救操作.下面是一个这样的例子:

multi  +OK  set key1 val1  +QUEUED  lpop key1  +QUEUED  incr num1  +QUEUED  exec  *3  +OK  -WRONGTYPE Operation against a key holding the wrong kind of value  :1

所有的命令序列都缓存成功,但是在提交事务的时候,命令set key1 val1和incr num1执行成功了,Redis保存了其执行结果,但是命令lpop key1执行失败了.

乐观锁机制

Redis事务和乐观锁一起使用时,事务将成为有条件提交.

关于乐观锁,需要注意的是:

  •  WATCH命令必须在MULTI命令之前执行. WATCH命令可以执行多次.

  •  WATCH命令可以指定乐观锁的多个key,如果在事务过程中,任何一个key被其他客户端改变,则当前客户端的乐观锁失败,事务提交时,将丢弃所有命令序列.

  •  多个客户端的WATCH命令可以指定相同的key.

WATCH命令指定乐观锁后,可以接着执行MULTI命令进入事务上下文,也可以在WATCH命令和MULTI命令之间执行其他命令. 具体使用方式取决于场景需求,不在事务中的命令将立即被执行.

如果WATCH命令指定的乐观锁的key,被当前客户端改变,在事务提交时,乐观锁不会失败.

如果WATCH命令指定的乐观锁的key具有超时属性,并且该key在WATCH命令执行后, 在事务提交命令EXEC执行前超时, 则乐观锁不会失败.如果该key被其他客户端对象修改,则乐观锁失败.

一个执行乐观锁机制的事务例子:

rpush list v1 v2 v3  :3  watch list  +OK  multi  +OK lpop list  +QUEUED  exec  *1  $2  v1

下面是另一个例子,乐观锁被当前客户端改变, 事务提交成功:

watch num  +OK  multi  +OK  incr num  +QUEUED  exec  *1  :2

Redis事务和乐观锁配合使用时, 可以构造实现单个Redis命令不能完成的更复杂的逻辑.

Redis事务的源码实现机制

首先,事务开始的MULTI命令执行的函数为multiCommand, 其实现为(multi.c):

void multiCommand(redisClient *c) {      if (c->flags & REDIS_MULTI) {          addReplyError(c,"MULTI calls can not be nested");          return;      }      c->flags |= REDIS_MULTI;      addReply(c,shared.ok);  }

该命令只是在当前客户端对象上加上REDIS_MULTI标志, 表示该客户端进入了事务上下文.

客户端进入事务上下文后,后续执行的命令将被缓存. 函数processCommand是Redis处理客户端命令的入口函数, 其实现为(redis.c):

int processCommand(redisClient *c) {            if (!strcasecmp(c->argv[0]->ptr,"quit")) {          addReply(c,shared.ok);          c->flags |= REDIS_CLOSE_AFTER_REPLY;          return REDIS_ERR;     }            c->ccmd = c->lastcmd = lookupCommand(c->argv[0]->ptr);      if (!c->cmd) {          flagTransaction(c);          addReplyErrorFORMat(c,"unknown command '%s'",              (char*)c->argv[0]->ptr);          return REDIS_OK;      } else if ((c->cmd->arity > 0 && c->cmd->arity != c->arGC) ||                 (c->argc < -c->cmd->arity)) {          flagTransaction(c);          addReplyErrorFormat(c,"wrong number of arguments for '%s' command",              c->cmd->name);          return REDIS_OK;     }            if (server.requirepass && !c->authenticated && c->cmd->proc != authCommand)      {          flagTransaction(c);          addReply(c,shared.noautherr);          return REDIS_OK;      }            if (server.maxmemory) {          int retval = freeMemoryIfNeeded();                    if (server.current_client == NULL) return REDIS_ERR;                    if ((c->cmd->flags & REDIS_CMD_DENYOOM) && retval == REDIS_ERR) {              flagTransaction(c);              addReply(c, shared.oomerr);              return REDIS_OK;          }      }            if (((server.stop_writes_on_bgsave_err &&            server.saveparamslen > 0 &&            server.lastbgsave_status == REDIS_ERR) ||            server.aof_last_write_status == REDIS_ERR) &&          server.masterhost == NULL &&          (c->cmd->flags & REDIS_CMD_WRITE ||           c->cmd->proc == pingCommand))      {          flagTransaction(c);          if (server.aof_last_write_status == REDIS_OK)              addReply(c, shared.bgsaveerr);          else              addReplySds(c,                  sdscatprintf(sdsempty(),                  "-MISCONF Errors writing to the AOF file: %s\r\n",                  strerror(server.aof_last_write_errno)));          return REDIS_OK;      }            if (server.masterhost == NULL &&          server.repl_min_slaves_to_write &&          server.repl_min_slaves_max_lag &&          c->cmd->flags & REDIS_CMD_WRITE &&          server.repl_Good_slaves_count < server.repl_min_slaves_to_write)      {          flagTransaction(c);          addReply(c, shared.noreplicaserr);          return REDIS_OK;      }            if (server.masterhost && server.repl_slave_ro &&          !(c->flags & REDIS_MASTER) &&          c->cmd->flags & REDIS_CMD_WRITE)      {          addReply(c, shared.roslaveerr);          return REDIS_OK;      }            if (c->flags & REDIS_PUBSUB &&          c->cmd->proc != pingCommand &&          c->cmd->proc != subscribeCommand &&          c->cmd->proc != unsubscribeCommand &&          c->cmd->proc != psubscribeCommand &&          c->cmd->proc != punsubscribeCommand) {          addReplyError(c,"only (P)SUBSCRIBE / (P)UNSUBSCRIBE / QUIT allowed in this context");          return REDIS_OK;      }            if (server.masterhost && server.repl_state != REDIS_REPL_CONNECTED &&          server.repl_serve_stale_data == 0 &&          !(c->cmd->flags & REDIS_CMD_STALE))      {          flagTransaction(c);          addReply(c, shared.masterdownerr);          return REDIS_OK;      }            if (server.loading && !(c->cmd->flags & REDIS_CMD_LOADING)) {          addReply(c, shared.loadingerr);          return REDIS_OK;      }            if (server.lua_timedout &&            c->cmd->proc != authCommand &&            c->cmd->proc != replconfCommand &&          !(c->cmd->proc == shutdownCommand &&            c->argc == 2 &&            tolower(((char*)c->argv[1]->ptr)[0]) == 'n') &&          !(c->cmd->proc == scriptCommand &&            c->argc == 2 &&            tolower(((char*)c->argv[1]->ptr)[0]) == 'k'))      {          flagTransaction(c);          addReply(c, shared.slowscripterr);          return REDIS_OK;      }            if (c->flags & REDIS_MULTI &&          c->cmd->proc != execCommand && c->cmd->proc != discardCommand &&          c->cmd->proc != multiCommand && c->cmd->proc != watchCommand)      {          queueMultiCommand(c);          addReply(c,shared.queued);      } else {          call(c,REDIS_CALL_FULL);          if (listLength(server.ready_keys))              handleClientsBlockedOnLists();      }      return REDIS_OK;  }

Line145:151当客户端处于事务上下文时, 如果接收的是非事务命令(MULTI, EXEC, WATCH, DISCARD), 则调用queueMultiCommand将命令缓存起来,然后向客户端发送成功响应.

在函数processCommand中, 在缓存命令之前, 如果检查到客户端发送的命令不存在,或者命令参数个数不正确等情况, 会调用函数flagTransaction标命令缓存失败.也就是说,函数processCommand中, 所有调用函数flagTransaction的条件分支,都是返回失败响应.

缓存命令的函数queueMultiCommand的实现为(multi.c):

  void queueMultiCommand(redisClient *c) {      multiCmd *mc;      int j;      c->mstate.commands = zrealloc(c->mstate.commands,              sizeof(multiCmd)*(c->mstate.count+1));      mc = c->mstate.commands+c->mstate.count;      mc->ccmd = c->cmd;      mc->argc = c->argc;      mc->argv = zmalloc(sizeof(robj*)*c->argc);      memcpy(mc->argv,c->argv,sizeof(robj*)*c->argc);      for (j = 0; j < c->argc; j++)          incrRefCount(mc->argv[j]);      c->mstate.count++;  }

在事务上下文中, 使用multiCmd结构来缓存命令, 该结构定义为(redis.h):

  typedef struct multiCmd {      robj **argv;      int argc;      struct redisCommand *cmd;  } multiCmd;

其中argv字段指向命令的参数内存地址,argc为命令参数个数, cmd为命令描述结构, 包括名字和函数指针等.

命令参数的内存空间已经使用动态分配记录于客户端对象的argv字段了, multiCmd结构的argv字段指向客户端对象redisClient的argv即可.

无法缓存命令时, 调用函数flagTransaction,该函数的实现为(multi.c):

  void flagTransaction(redisClient *c) {      if (c->flags & REDIS_MULTI)          c->flags |= REDIS_DIRTY_EXEC;  }

该函数在客户端对象中设置REDIS_DIRTY_EXEC标志, 如果设置了这个标志, 事务提交时, 命令序列将被丢弃.

最后,在事务提交时, 函数processCommand中将调用call(c,REDIS_CALL_FULL);, 其实现为(redis.c):

  void call(redisClient *c, int flags) {      long long dirty, start, duration;      int cclient_old_flags = c->flags;            if (listLength(server.monitors) &&          !server.loading &&          !(c->cmd->flags & (REDIS_CMD_SKIP_MONITOR|REDIS_CMD_ADMIN)))      {          replicationFeedMonitors(c,server.monitors,c->db->id,c->argv,c->argc);     }            c->flags &= ~(REDIS_FORCE_AOF|REDIS_FORCE_REPL);      redisOpArrayInit(&server.also_propagate);      dirty = server.dirty;      start = ustime();      c->cmd->proc(c);      duration = ustime()-start;      dirty = server.dirty-dirty;      if (dirty < 0) dirty = 0;            if (server.loading && c->flags & REDIS_LUA_CLIENT)          flags &= ~(REDIS_CALL_SLOWLOG | REDIS_CALL_STATS);            if (c->flags & REDIS_LUA_CLIENT && server.lua_caller) {          if (c->flags & REDIS_FORCE_REPL)              server.lua_caller->flags |= REDIS_FORCE_REPL;          if (c->flags & REDIS_FORCE_AOF)              server.lua_caller->flags |= REDIS_FORCE_AOF;      }           if (flags & REDIS_CALL_SLOWLOG && c->cmd->proc != execCommand) {          char *latency_event = (c->cmd->flags & REDIS_CMD_FAST) ?                                "fast-command" : "command";          latencyAddSampleIfNeeded(latency_event,duration/1000);          slowlogPushEntryIfNeeded(c->argv,c->argc,duration);      }      if (flags & REDIS_CALL_STATS) {          c->cmd->microseconds += duration;          c->cmd->calls++;      }            if (flags & REDIS_CALL_PROPAGATE) {          int flags = REDIS_PROPAGATE_NONE;          if (c->flags & REDIS_FORCE_REPL) flags |= REDIS_PROPAGATE_REPL;          if (c->flags & REDIS_FORCE_AOF) flags |= REDIS_PROPAGATE_AOF;          if (dirty)              flags |= (REDIS_PROPAGATE_REPL | REDIS_PROPAGATE_AOF);          if (flags != REDIS_PROPAGATE_NONE)              propagate(c->cmd,c->db->id,c->argv,c->argc,flags);      }            c->flags &= ~(REDIS_FORCE_AOF|REDIS_FORCE_REPL);      c->flags |= client_old_flags & (REDIS_FORCE_AOF|REDIS_FORCE_REPL);            if (server.also_propagate.numops) {          int j;          redisOp *rop;          for (j = 0; j < server.also_propagate.numops; j++) {              rop = &server.also_propagate.ops[j];              propagate(rop->cmd, rop->dbid, rop->argv, rop->argc, rop->target);          }          redisOpArrayFree(&server.also_propagate);      }      server.stat_numcommands++;  }

在函数call中通过执行c->cmd->proc(c);调用具体的命令函数.事务提交命令EXEC对应的执行函数为execCommand, 其实现为(multi.c):

void execCommand(redisClient *c) {      int j;      robj **orig_argv;      int orig_argc;      struct redisCommand *orig_cmd;      int must_propagate = 0;       if (!(c->flags & REDIS_MULTI)) {          addReplyError(c,"EXEC without MULTI");          return;      }            if (c->flags & (REDIS_DIRTY_CAS|REDIS_DIRTY_EXEC)) {          addReply(c, c->flags & REDIS_DIRTY_EXEC ? shared.execaborterr :                                                    shared.nullmultibulk);          discardTransaction(c);          goto handle_monitor;      }            unwatchAllKeys(c);       orig_argv = c->argv;      orig_argc = c->argc;      orig_cmd = c->cmd;      addReplyMultiBulkLen(c,c->mstate.count);      for (j = 0; j < c->mstate.count; j++) {          c->argc = c->mstate.commands[j].argc;          c->argv = c->mstate.commands[j].argv;          c->ccmd = c->mstate.commands[j].cmd;                    if (!must_propagate && !(c->cmd->flags & REDIS_CMD_READONLY)) {              execCommandPropagateMulti(c);              must_propagate = 1;          }          call(c,REDIS_CALL_FULL);                    c->mstate.commands[j].argc = c->argc;          c->mstate.commands[j].argv = c->argv;          c->mstate.commands[j].cmd = c->cmd;      }      c->argv = orig_argv;      c->argc = orig_argc;      c->cmd = orig_cmd;      discardTransaction(c);            if (must_propagate) server.dirty++; handle_monitor:            if (listLength(server.monitors) && !server.loading)          replicationFeedMonitors(c,server.monitors,c->db->id,c->argv,c->argc);  }

LINE8:11检查EXEC命令和MULTI命令是否配对使用, 单独执行EXEC命令是没有意义的.

LINE19:24检查客户端对象是否具有REDIS_DIRTY_CAS或者REDIS_DIRTY_EXEC标志, 如果存在,则调用函数discardTransaction丢弃命令序列, 向客户端返回失败响应.

如果没有检查到任何错误,则先执行unwatchAllKeys(c);取消该客户端上所有的乐观锁key.

LINE32:52依次执行缓存的命令序列,这里有两点需要注意的是:

事务可能需要同步到AOF缓存或者replica备份节点中.如果事务中的命令序列都是读操作, 则没有必要向AOF和replica进行同步.如果事务的命令序列中包含写命令,则MULTI, EXEC和相关的写命令会向AOF和replica进行同步.根据LINE41:44的条件判断,执行execCommandPropagateMulti(c);保证MULTI命令同步, LINE59检查EXEC命令是否需要同步, 即MULTI命令和EXEC命令必须保证配对同步.EXEC命令的同步执行在函数的call中LINE62propagate(c->cmd,c->db->id,c->argv,c->argc,flags);, 具体的写入命令由各自的执行函数负责同步.

这里执行命令序列时, 通过执行call(c,REDIS_CALL_FULL);所以call函数是递归调用.

所以,综上所述, Redis事务其本质就是,以不可中断的方式依次执行缓存的命令序列,将结果保存到内存cache中.

事务提交时, 丢弃命令序列会调用函数discardTransaction, 其实现为(multi.c):

void discardTransaction(redisClient *c) {      freeClientMultiState(c);      initClientMultiState(c);      c->flags &= ~(REDIS_MULTI|REDIS_DIRTY_CAS|REDIS_DIRTY_EXEC);      unwatchAllKeys(c);  }

该函数调用freeClientMultiState释放multiCmd对象内存.调用initClientMultiState复位客户端对象的缓存命令管理结构.调用unwatchAllKeys取消该客户端的乐观锁.

WATCH命令执行乐观锁, 其对应的执行函数为watchCommand, 其实现为(multi.c):

void watchCommand(redisClient *c) {      int j;      if (c->flags & REDIS_MULTI) {          addReplyError(c,"WATCH inside MULTI is not allowed");          return;      }      for (j = 1; j < c->argc; j++)          watchForKey(c,c->argv[j]);      addReply(c,shared.ok);  }

进而调用函数watchForKey, 其实现为(multi.c):

  void watchForKey(redisClient *c, robj *key) {      list *clients = NULL;      listIter li;      listnode *ln;      watchedKey *wk;            listRewind(c->watched_keys,&li);      while((ln = listNext(&li))) {          wk = listNodeValue(ln);          if (wk->db == c->db && equalStringObjects(key,wk->key))              return;       }            clients = dictFetchValue(c->db->watched_keys,key);      if (!clients) {          clients = listCreate();          dictAdd(c->db->watched_keys,key,clients);          incrRefCount(key);      }      listAddNodeTail(clients,c);            wk = zmalloc(sizeof(*wk));      wk->keykey = key;      wk->db = c->db;      incrRefCount(key);      listAddNodeTail(c->watched_keys,wk);  }

关于乐观锁的key, 既保存于其客户端对象的watched_keys链表中, 也保存于全局数据库对象的watched_keys哈希表中.

LINE10:14检查客户端对象的链表中是否已经存在该key, 如果已经存在, 则直接返回.LINE16在全局数据库中返回该key对应的客户端对象链表, 如果链表不存在, 说明其他客户端没有使用该key作为乐观锁, 如果链表存在, 说明其他客户端已经使用该key作为乐观锁. LINE22将当前客户端对象记录于该key对应的链表中. LINE28将该key记录于当前客户端的key链表中.

当前客户端执行乐观锁以后, 其他客户端的写入命令可能修改该key值.所有具有写操作属性的命令都会执行函数signalModifiedKey, 其实现为(db.c):

void signalModifiedKey(redisDb *db, robj *key) {      touchWatchedKey(db,key);  }

函数touchWatchedKey的实现为(multi.c):

  void touchWatchedKey(redisDb *db, robj *key) {      list *clients;      listIter li;      listNode *ln;      if (dictSize(db->watched_keys) == 0) return;      clients = dictFetchValue(db->watched_keys, key);      if (!clients) return;                 listRewind(clients,&li);      while((ln = listNext(&li))) {          redisClient *c = listNodeValue(ln);          c->flags |= REDIS_DIRTY_CAS;      }  }

语句if (dictSize(db->watched_keys) == 0) return;检查全局数据库中的哈希表watched_keys是否为空, 如果为空,说明没有任何客户端执行WATCH命令, 直接返回.如果该哈希表不为空, 取回该key对应的客户端链表结构,并把该链表中的每个客户端对象设置REDIS_DIRTY_CAS标志. 前面在EXEC的执行命令中,进行过条件判断, 如果客户端对象具有这个标志, 则丢弃事务中的命令序列.

在执行EXEC, DISCARD, UNWATCH命令以及在客户端结束连接的时候,都会取消乐观锁, 最终都会执行函数unwatchAllKeys, 其实现为(multi.c):

  void unwatchAllKeys(redisClient *c) {      listIter li;      listNode *ln;      if (listLength(c->watched_keys) == 0) return;      listRewind(c->watched_keys,&li);      while((ln = listNext(&li))) {          list *clients;          watchedKey *wk;                    wk = listNodeValue(ln);          clients = dictFetchValue(wk->db->watched_keys, wk->key);          redisAssertWithInfo(c,NULL,clients != NULL);          listDelNode(clients,listSearchKey(clients,c));                    if (listLength(clients) == 0)              dictDelete(wk->db->watched_keys, wk->key);                    listDelNode(c->watched_keys,ln);          decrRefCount(wk->key);          zfree(wk);      }  }

语句if (listLength(c->watched_keys) == 0) return;判断如果当前客户端对象的watched_keys链表为空,说明当前客户端没有执行WATCH命令,直接返回.如果该链表非空, 则依次遍历该链表中的key, 并从该链表中删除key, 同时,获得全局数据库中的哈希表watched_keys中该key对应的客户端链表, 删除当前客户端对象.

到此,相信大家对“如何深入理解Redis事务”有了更深的了解,不妨来实际操作一番吧!这里是编程网网站,更多相关内容可以进入相关频道进行查询,关注我们,继续学习!

您可能感兴趣的文档:

--结束END--

本文标题: 如何深入理解Redis事务

本文链接: https://www.lsjlt.com/news/62011.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • 如何深入理解Redis事务
    本篇内容主要讲解“如何深入理解Redis事务”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“如何深入理解Redis事务”吧!Redis可以看成NoSQL类型的数据...
    99+
    2024-04-02
  • Python:深入理解Redis事务
    1.从数据库事务说起通常我们提及数据库都不可避免的要提到事务,那么什么是事务呢事务是指作为单个逻辑工作单元执行的一系列操作。所以,首先事务是一系列操作,这一系列操作具有二态性,即完全地执行或者完全地不执行。因此事务处理可以确保除非事务单元内...
    99+
    2023-06-01
  • 如何深入理解Redis分布式锁
    这篇文章主要介绍“如何深入理解Redis分布式锁”,在日常操作中,相信很多人在如何深入理解Redis分布式锁问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”如何深入理解Redi...
    99+
    2024-04-02
  • 如何深入解析JavaWeb中的事务
    如何深入解析JavaWeb中的事务,相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。事务,大家所熟悉的事务(Transcation),基本上会就往...
    99+
    2024-04-02
  • 如何深入了解Redis中的Codis
    这篇文章给大家介绍如何深入了解Redis中的Codis,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。场景在大数据高并发场景下,使用单个redis实例,即使redis的性能再高,也会变的...
    99+
    2024-04-02
  • 深入理解redis中multi与pipeline
    背景 由于对redis缓存中数据有批量操作,例如预热缓存数据,或者在列表页批量去获取缓存数据,在使用了multi批量提交事务后,发现redis压力高居不下,而使用了pipeline...
    99+
    2024-04-02
  • 深入理解redis的持久化
    最近工作之余学习了一下redis,这里简单的理解一下redis持久化; Redis提供的持久化机制 Redis是一种面向“key-value”类型数据的分布式NoSQL数据库系统,具有高...
    99+
    2024-04-02
  • 深入解析MySQL 事务
    目录事务的四大特性 ( ACID )脏读不可重复读幻读MySQL的隔离级别事务指逻辑上的一组操作,组成这组操作的各个单元,要么全部成功,要么全部失败。 事务的四大特性 ( ACID ...
    99+
    2024-04-02
  • 如何深入理解select (*)
    这期内容当中小编将会给大家带来有关如何深入理解select count(*),文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。SELECT COUNT( * ) F...
    99+
    2024-04-02
  • Redis中如何深入了解Makefile文件
    这篇文章的内容主要围绕Redis中如何深入了解Makefile文件进行讲述,文章内容清晰易懂,条理清晰,非常适合新手学习,值得大家去阅读。感兴趣的朋友可以跟随小编一起阅读吧。希望大家通过这篇文章有所收获!M...
    99+
    2024-04-02
  • Redis字符串原理的深入理解
    前言 来掘进都有两年多了一直当个小透明,今天终于发一次文章了. 最近在看 Redis,感觉收获很多,写篇博客记录一下. Redis 有五种基础数据结构:string,list,set,zset,has...
    99+
    2024-04-02
  • 深入理解JavaScript事件机制
    目录如何实现一个事件的发布订阅介绍下事件循环宏任务和微任务的区别如何实现一个事件的发布订阅 可以通过以下步骤实现 JavaScript 中的发布-订阅模式: 创建一个事件管理器对象。...
    99+
    2023-05-17
    JavaScript事件机制 JS事件机制
  • 深入理解mysql事务与存储引擎
    目录一、MySQL事务1、事务的概念2、事务的 ACID 特点3、事物之间的互相影响二、Mysql及事务隔离级别1、查询全局事务隔离级别2、查询会话事务隔离级别3、设置全局事务隔离级...
    99+
    2024-04-02
  • 深入理解异步事件机制
    通过了解异步设计的由来,来深入理解异步事件机制。 代码地址 什么是异步 同步 并发(Concurrency) 线程(Thread) I/O多路复用 异步(Asynchronous) 回调(Callback) 参考文...
    99+
    2023-01-31
    机制 事件
  • Qt 事件处理机制的深入理解
    目录1.Qt中事件的来源,谁接收处理。2.事件处理顺序3.事件过滤器4.event方法5.鼠标进入事件6.accept(),ignore()1.Qt中事件的来源,谁接收处理。 Qt中...
    99+
    2024-04-02
  • 如何深入理解MySQL索引
    这篇文章将为大家详细讲解有关如何深入理解MySQL索引,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。前言当提到MySQL数据库的时候,我们的脑海里会想起几个...
    99+
    2024-04-02
  • JavaScript中如何深入理解this
    JavaScript中如何深入理解this,相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。定义this是函数运行时自动生成的内部对象,即调用函数...
    99+
    2024-04-02
  • 深入理解C#窗体关闭事件
    目录1、第一步2、第二步3、最后可以检查一下设计designer.cs的文件看看对不对很多初学者都想把默认的C#关闭按钮事件弄明白,主要用在我想关闭窗口但是我还想在关闭前有一个提示,...
    99+
    2024-04-02
  • 深入理解MySQL事务的4种隔离级别
    目录1 简介2 什么是数据库事务?2.1 事务的四大特性(ACID)3 并发事务会导致的问题3.1 本文会使用到的 SQL 语句3.1.1 示例表结构3.1.2 查询事务的默认隔离级...
    99+
    2024-04-02
  • 带大家深入了解Spring事务
    目录一、数据库事务简介二、事务的特性三、事务的隔离级别四、Spring事务五、PlatformTransactionManager简介六、Spring事务隔离级别和传播级别一、数据库...
    99+
    2024-04-02
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作