查看“Redis:事务(transaction)”的源代码
←
Redis:事务(transaction)
跳到导航
跳到搜索
因为以下原因,您没有权限编辑本页:
您请求的操作仅限属于该用户组的用户执行:
用户
您可以查看和复制此页面的源代码。
[[category:Redis]] == 关于“Redis事务” == <pre> Redis 中的事务(transaction)是一组命令的集合,至少是两个或两个以上的命令,Redis 事务保证这些命令被执行时中间不会被任何其他操作打断。 </pre> 事务可以一次执行多个命令, 并且带有以下两个重要的保证: # 事务是一个单独的'''隔离操作''':事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。 # 事务是一个'''原子操作''':事务中的命令要么全部被执行,要么全部都不执行。 当使用 AOF 方式做持久化的时候,Redis 会使用单个 write(2) 命令将事务写入到磁盘中。然而,如果 Redis 服务器因为某些原因被管理员杀死,或者遇上某种硬件故障,那么可能只有部分事务命令会被成功写入到磁盘中: 1、如果 Redis 在重新启动时发现 AOF 文件出了这样的问题,那么它会退出,并汇报一个错误。 2、使用 '''redis-check-aof''' 程序可以修复这一问题:它会移除 AOF 文件中不完整事务的信息,确保服务器可以顺利启动。 从 2.2 版本开始,Redis 还可以通过'''乐观锁'''(optimistic lock)实现 '''CAS''' (check-and-set)操作。 == 事务命令 == 一个事务从开始到执行会经历以下三个阶段: # 开始事务。 # 命令入队。 # 执行/放弃事务。 而 MULTI 、 EXEC 、 DISCARD 和 WATCH 是 Redis 事务的基础: {| class="wikitable" |+ Redis事务命令 ! 命令 !! 描述 |- | MULTI || 标记一个事务块的开始。 * 它总是返回 OK。 * 客户端可以继续向服务器发送任意多条命令,所有传入的命令都会返回一个内容为 '''QUEUED''' 的状态回复(status reply)。 ** 这些命令被放到一个'''队列'''中,当 EXEC 命令被调用时,队列中的所有命令才会被按序执行。 |- | EXEC || 执行事务内所有的命令。 * 如果没有成功执行 EXEC(因为断线等),那么事务中的所有命令都不会被执行;如果成功执行 EXEC,那么事务中的所有命令都会被执行。 * 其回复是一个数组,数组中的每个元素都是执行事务中的命令所产生的回复(回复元素的先后顺序和命令发送的先后顺序一致)。 |- | DISCARD || 取消事务。 * 事务会被放弃,事务队列会被清空,并且客户端会从事务状态中退出。 |- | WATCH key [key ...] || 监视一个(或多个) key。 * 如果在事务执行之前这个(或这些) key 被其他命令所改动,那么事务就不会被执行。 |- | UNWATCH || 取消 WATCH 命令对所有 key 的监视。 |} === 示例 === # 示例1:以 MULTI 开始一个事务, 然后将多个命令入队到事务中, 最后由 EXEC 命令触发事务, 一并执行事务中的所有命令: #: <syntaxhighlight lang="bash" highlight=""> redis 127.0.0.1:6379> MULTI OK redis 127.0.0.1:6379> SET book-name "Mastering C++ in 21 days" QUEUED redis 127.0.0.1:6379> GET book-name QUEUED redis 127.0.0.1:6379> SADD tag "C++" "Programming" "Mastering Series" QUEUED redis 127.0.0.1:6379> SMEMBERS tag QUEUED redis 127.0.0.1:6379> EXEC 1) OK 2) "Mastering C++ in 21 days" 3) (integer) 3 4) 1) "Mastering Series" 2) "C++" 3) "Programming" </syntaxhighlight> # 示例二:执行 DISCARD 命令时, 事务会被放弃, 事务队列会被清空, 并且客户端会从事务状态中退出: #: <syntaxhighlight lang="bash" highlight=""> redis> SET foo 1 OK redis> MULTI OK redis> INCR foo QUEUED redis> DISCARD OK redis> GET foo "1" </syntaxhighlight> == 事务中的错误 == 使用事务时可能会遇上两种错误: # 在执行 EXEC 之前,命令入队出错。 #: 比如:命令可能会产生语法错误(参数数量错误,参数名错误,等等);或者其他更严重的错误,比如内存不足(如果服务器使用 maxmemory 设置了最大内存限制的话)。 # 在执行 EXEC 之后,命令执行出错。 #: 比如:事务中的命令可能处理了错误类型的键,比如将列表命令用在了字符串键上面,诸如此类。 === 在执行 EXEC 之前,命令入队出错 === 命令入队时出错,将会导致'''放弃事务''': <pre> 对于发生在 EXEC 执行之前的错误,客户端以前的做法是检查命令入队所得的返回值:如果命令入队时返回 QUEUED ,那么入队成功;否则,就是入队失败。 如果有命令在入队时失败,那么大部分客户端都会停止并取消这个事务。 1、在 Redis 2.6.5 以前, Redis 只执行事务中那些入队成功的命令,而忽略那些入队失败的命令。而新的处理方式则使得在流水线(pipeline)中包含事务变得简单,因为发送事务和读取事务的回复都只需要和服务器进行一次通讯。 2、从 Redis 2.6.5 开始,服务器会对命令入队失败的情况进行记录,并在客户端调用 EXEC 命令时,拒绝执行并自动放弃这个事务。 </pre> 示例: : 因为调用 INCR 命令的参数格式不正确, 所以这个 INCR 命令入队失败: : <syntaxhighlight lang="bash" highlight=""> MULTI +OK INCR a b c -ERR wrong number of arguments for 'incr' command </syntaxhighlight> === 在执行 EXEC 之后,命令执行出错 === 命令执行期间出错,'''事务队列中的其他命令将会继续执行''': <pre> 至于那些在 EXEC 命令执行之后所产生的错误, 并没有对它们进行特别处理: 即使事务中有某个/某些命令在执行时产生了错误, 事务中的其他命令仍然会继续执行。 </pre> 示例: : LPOP 命令的执行将出错,尽管调用它的语法是正确的: : <syntaxhighlight lang="bash" highlight=""> MULTI +OK SET a 3 abc +QUEUED LPOP a +QUEUED EXEC *2 +OK -ERR Operation against a key holding the wrong kind of value </syntaxhighlight> == WATCH == WATCH 使得 EXEC 命令需要有条件地执行:事务只能在所有被监视键都没有被修改的前提下执行,如果这个前提不能满足的话,事务就不会被执行。 监视的使用'''【注意】''': * WATCH 命令可以被调用多次。对键的监视从 WATCH 执行之后开始生效,直到调用 EXEC 为止。 * 还可以在单个 WATCH 命令中监视任意多个键: *: <syntaxhighlight lang="bash" highlight=""> redis> WATCH key1 key2 key3 OK </syntaxhighlight> * 如果使用 WATCH 监视了一个带过期时间的键,那么即使这个键过期了,事务仍然可以正常执行。 监视的取消'''【注意】''': * 当 EXEC 被调用时,不管事务是否成功执行,对所有键的监视都会被取消。 * 使用无参数的 UNWATCH 命令可以手动取消对所有键的监视。 * 当客户端断开连接时,该客户端对键的监视也会被取消。 === 使用 Watch 实现 ZPOP === WATCH 可以用于创建 Redis 没有内置的原子操作。 示例:以下代码实现了原创的 ZPOP 命令, 它可以原子地弹出有序集合中分值(score)最小的元素: <syntaxhighlight lang="bash" highlight=""> WATCH zset element = ZRANGE zset 0 0 MULTI ZREM zset element EXEC </syntaxhighlight> 程序只要重复执行这段代码, 直到 EXEC 的返回值不是多条空回复(null multi-bulk reply)即可。 === 使用 Watch 操作实现乐观锁(check-and-set) === WATCH 命令可以为 Redis 事务提供 check-and-set(CAS)行为: 被 WATCH 的键会被监视,并会发觉这些键是否被改动过了: 如果有至少一个被监视的键在 EXEC 执行之前被修改了,那么整个事务都会被取消,EXEC 返回空多条批量回复(null multi-bulk reply)来表示事务已经失败。 举个例子,假设我们需要原子性地为某个值进行增 1 操作(假设 INCR 不存在)。 : 首先我们可能会这样做: : <syntaxhighlight lang="bash" highlight=""> val = GET mykey val = val + 1 SET mykey $val </syntaxhighlight> : 上面的这个实现在只有一个客户端的时候可以执行得很好。但是,当多个客户端同时对同一个键进行这样的操作时,就会产生竞争条件: :: 比如:客户端 A 和 B 都读取了键原来的值,比如 10,那么两个客户端都会将键的值设为 11,但正确的结果应该是 12 才对。 有了 WATCH,我们就可以轻松地解决这类问题了: : <syntaxhighlight lang="bash" highlight=""> WATCH mykey val = GET mykey val = val + 1 MULTI SET mykey $val EXEC </syntaxhighlight> : 使用上面的代码, 如果在 WATCH 执行之后, EXEC 执行之前, 有其他客户端修改了 mykey 的值, 那么当前客户端的事务就会失败。 程序需要做的, 就是不断重试这个操作, 直到没有发生碰撞为止。 这种形式的锁被称作乐观锁,它是一种非常强大的锁机制。并且因为大多数情况下,不同的客户端会访问不同的键,碰撞的情况一般都很少,所以通常并不需要进行重试。 == Redis 脚本和事务 == 从定义上来说, '''Redis 中的脚本本身就是一种事务''',所以任'''何在事务里可以完成的事,在脚本里面也能完成'''。并且一般来说,使用脚本要来得更简单,并且速度更快。 因为脚本功能是 Redis 2.6 才引入的,而事务功能则更早之前就存在了,所以 Redis 才会同时存在两种处理事务的方法。 == FAQ == === 【关于:Redis 事务“不支持回滚,不保证原子性”】 === 单个 Redis 命令的执行是原子性的,但 Redis 没有在事务上增加任何维持原子性的机制,所以“Redis 事务的执行并不是原子性的”: '''事务可以理解为一个打包的批量执行脚本,但批量指令并非原子化的操作,中间某条指令的失败不会导致前面已做指令的回滚,也不会造成后续的指令不做'''。 即:【<big>'''不支持回滚,不保证原子性'''</big>】(支持回滚的复杂操作会使 Redis 放弃其简单高效的特性) 如: <syntaxhighlight lang="bash" highlight=""> redis 127.0.0.1:7000> multi OK redis 127.0.0.1:7000> set a aaa QUEUED redis 127.0.0.1:7000> set b bbb QUEUED redis 127.0.0.1:7000> set c ccc QUEUED redis 127.0.0.1:7000> exec 1) OK 2) ERR - . . . 3) OK </syntaxhighlight> 如果 exec 在执行 set b bbb 处失败,set a 已成功不会回滚,set c 还会继续执行。 === 【关于:“原子操作”和“原子性”】 === Redis 事务是一个'''原子操作''',即:“事务中的命令要么全部被执行,要么全部都不执行”。 : 开启了事务之后:如果没有成功执行 EXEC,那么事务中的所有命令都不会被执行;如果成功执行 EXEC,那么事务中的所有命令都会被执行。 Redis 事务并不能保证操作的“'''原子性'''”,即:“该成功的成功,该失败的失败”。 : Redis 收到 EXEC 命令后进入事务执行,事务中任意命令执行失败,其余的命令依然被执行。 === 为什么 Redis 不支持回滚(roll back)? === 不同于关系式数据库,“Redis 在事务失败时不进行回滚,而是继续执行余下的命令”,其优点在于: # Redis 命令只会因为错误的语法而失败(并且这些问题不能在入队时发现),或是命令用在了错误类型的键上面:这也就是说,从实用性的角度来说,失败的命令是由编程错误造成的,而这些错误应该在开发的过程中被发现,而不应该出现在生产环境中。 # 因为不需要对回滚进行支持,所以 Redis 的内部可以保持简单且快速。 有种观点认为 Redis 处理事务的做法会产生 bug,然而需要注意的是,在通常情况下,回滚并不能解决编程错误带来的问题。举个例子,如果你本来想通过 INCR 命令将键的值加上 1,却不小心加上了 2,又或者对错误类型的键执行了 INCR,回滚是没有办法处理这些情况的。 鉴于没有任何机制能避免程序员自己造成的错误,并且这类错误通常不会在生产环境中出现,所以 Redis 选择了更简单、更快速的无回滚方式来处理事务。
返回至“
Redis:事务(transaction)
”。
导航菜单
个人工具
登录
命名空间
页面
讨论
大陆简体
已展开
已折叠
查看
阅读
查看源代码
查看历史
更多
已展开
已折叠
搜索
导航
首页
最近更改
随机页面
MediaWiki帮助
笔记
服务器
数据库
后端
前端
工具
《To do list》
日常
阅读
电影
摄影
其他
Software
Windows
WIKIOE
所有分类
所有页面
侧边栏
站点日志
工具
链入页面
相关更改
特殊页面
页面信息