查看“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> == Redis事务复杂情况实现 == === 悲观锁 === 悲观锁(Pessimistic Lock):每次去拿数据的时候都认为别人会修改该数据,所以每次在拿数据的时候都会先上锁,这样别人想拿这个数据就会 block 阻塞直到它拿到锁。 * 传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁,让别人无法操作该数据。 === 乐观锁 === 乐观锁(Optimistic Lock):每次去取数据的时候都认为别人不会修改该数据,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这条数据,一般使用'''版本号机制'''(类比于MySQL的“MVCC”)进行判断。 * 乐观锁适用于多读的应用类型,这样可以提高吞吐量。 * 乐观锁大多数情况是基于数据版本号(version)的机制实现的:为数据增加一个版本标识。 *: 在基于数据库表的版本解决方案中,一般是通过为数据库表添加一个“version”字段来实现读取出数据时,将此版本号一同读出,之后更新时,对此版本号加 1。此时,将提交数据的版本号与数据库表对应记录的当前版本号进行比对,如果提交的数据版本号大于数据库表当前版本号,则予以更新,否则认为是过期数据,不予更新。 乐观锁实现举例: : [[File:乐观锁实现举例.png|600px]] === '''watch 机制'''实现乐观锁 === 监视一个(或多个)key ,'''如果在事务exec执行之前这个(或这些)key 被其他命令所改动,那么事务将被打断'''。 示例: # set k1 1:设置 k1 值为 1; # watch k1:监视 k1;【开始监视 k1,则其他客户端或事务不能修改 k1 的值】 # set k1 2:设置 k1 值为 2; # multi:开始事务; # set k1 3:修改 k1 值为 3; # exec:提交事务; 如上,k1 的值仍然是 2,而不会被修改为 3,因为在事务开启之前 k1 的值被修改了; == 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
所有分类
所有页面
侧边栏
站点日志
工具
链入页面
相关更改
特殊页面
页面信息