查看“Redis:事务(transaction)”的源代码
←
Redis:事务(transaction)
跳到导航
跳到搜索
因为以下原因,您没有权限编辑本页:
您请求的操作仅限属于该用户组的用户执行:
用户
您可以查看和复制此页面的源代码。
[[category:Redis]] == 什么是事务? == <pre> 事务:是指一系列操作步骤,要么完全地执行,要么完全地不执行。 </pre> 事务可以一次执行多个命令, 并且带有以下两个重要的保证: # 事务是一个单独的'''隔离操作''':事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。 # 事务是一个'''原子操作''':事务中的命令要么全部被执行,要么全部都不执行。 Redis 中的事务(transaction)是一组命令的集合,至少是两个或两个以上的命令,redis 事务保证这些命令被执行时中间不会被任何其他操作打断。<br/> == Redis事务控制 == Redis 事务可以一次执行多个命令, 并且带有以下三个重要的保证: # 批量操作在发送 EXEC 命令前被放入队列缓存。 # 收到 EXEC 命令后进入事务执行,事务中任意命令执行失败,其余的命令依然被执行。 # 在事务执行过程,其他客户端提交的命令请求不会插入到事务执行命令序列中。 注意: * '''Redis 事务不支持回滚'''。(支持回滚的复杂操作会使 Redis 放弃其简单高效的特性) * '''Redis 事务不保证原子性'''。(该成功的成功,该失败的失败) 一个事务从开始到执行会经历以下三个阶段: # 开始事务。 # 命令入队。 # 执行事务。 === 事务命令 === {| class="wikitable" ! 命令 !! 描述 |- | MULTI || 标记一个事务块的开始。 |- | EXEC || 执行所有事务块内的命令。 |- | DISCARD || 取消事务,放弃执行事务块内的所有命令。 |- | WATCH key [key ...] || 监视一个(或多个) key ,如果在事务执行之前这个(或这些) key 被其他命令所改动,那么事务将被打断。 |- | UNWATCH || 取消 WATCH 命令对所有 key 的监视。 |} # '''MULTI''' :用于'''开启事务''',它总是返回 OK。 #* MULTI 执行之后, 客户端可以继续向服务器发送任意多条命令, 这些命令不会立即被执行, 而是被放到一个'''队列'''中, 当 EXEC 命令被调用时, 所有队列中的命令才会被执行。 #* 调用 DISCARD , 客户端可以清空事务队列, 并放弃执行事务。 # '''EXEC''':负责'''触发并执行'''事务中的所有命令: #* 如果客户端使用 MULTI 开启事务之后,却因为断线而没有成功执行 EXEC,那么事务中的所有命令都不会被执行;如果开启事务之后成功执行 EXEC,那么事务中的所有命令都会被执行。 #* EXEC 命令的回复是一个数组,数组中的每个元素都是执行事务中的命令所产生的回复(回复元素的先后顺序和命令发送的先后顺序一致)。 # '''DISCARD''':用于'''放弃事务''',事务队列会被清空,并且客户端会从事务状态中退出。 # '''WATCH''' 使得 EXEC 命令需要有条件地执行:事务只能在所有被监视键都没有被修改的前提下执行,如果这个前提不能满足的话,事务就不会被执行。 <pre> 当使用 AOF 方式做持久化的时候, Redis 会使用单个 write(2) 命令将事务写入到磁盘中。 然而,如果 Redis 服务器因为某些原因被管理员杀死,或者遇上某种硬件故障,那么可能只有部分事务命令会被成功写入到磁盘中。 如果 Redis 在重新启动时发现 AOF 文件出了这样的问题,那么它会退出,并汇报一个错误。 使用 【redis-check-aof】 程序可以修复这一问题:它会移除 AOF 文件中不完整事务的信息,确保服务器可以顺利启动。 </pre> 当客户端处于事务状态时, 所有传入的命令都会返回一个内容为 '''QUEUED''' 的状态回复(status reply), 这些被入队的命令将在 EXEC 命令被调用时执行。 === 事务示例 === 以下是一个事务的例子, 它先以 MULTI 开始一个事务, 然后将多个命令入队到事务中, 最后由 EXEC 命令触发事务, 一并执行事务中的所有命令: <syntaxhighlight lang="java" 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> 单个 Redis 命令的执行是原子性的,但 Redis 没有在事务上增加任何维持原子性的机制,所以 '''Redis 事务的执行并不是原子性的'''。 '''事务可以理解为一个打包的批量执行脚本,但批量指令并非原子化的操作,中间某条指令的失败不会导致前面已做指令的回滚,也不会造成后续的指令不做'''。【<big>'''不支持回滚,不保证原子性'''</big>】 如下: <syntaxhighlight lang="java" 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) OK 3) OK </syntaxhighlight> 如果在 set b bbb 处失败,set a 已成功不会回滚,set c 还会继续执行。 === 事务情况【???】 === Redis 事务不支持回滚,'''如果遇到问题,会继续执行余下的命令'''。 * '''只有语法错误,Redis 才会执行失败'''。 正常情况: # MULTI:用MULTI命令告诉Redis,接下来要执行的命令你先不要执行,而是把它们暂时存起来 【开启事务】 # SADD "user1" 2:第一条命令进入等待队列【命令入队】 # SADD "user2" 1:第二条命令进入等待队列【命令入队】 # EXEC:告知redis执行前面发送的两条命令【提交事务】 异常情况:【???】 # MULTI:正常命令 # SET key value:正常命令 # SET key:命令语法错误 # EXEC:无法执行事务,那么第一条正确的命令也不会执行,所以key的值不会设置成功 例外情况: # MULTI:正常命令 # SET key v1:正常命令 # INCR key:此命令错误,字符串不能自增 # EXEC: ## 事务依然提交了,key的值被设置为v1,自增操作执行失败,但整个事务没有回滚 ## 可以放弃事务:discard 放弃情况: # MULTI:开启事务 # SET age 25:命令入队 # SET age 30:命令入队 # DISCARD:放弃事务,则命令队列不会被执行 == 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 的值被修改了;
返回至“
Redis:事务(transaction)
”。
导航菜单
个人工具
登录
命名空间
页面
讨论
大陆简体
已展开
已折叠
查看
阅读
查看源代码
查看历史
更多
已展开
已折叠
搜索
导航
首页
最近更改
随机页面
MediaWiki帮助
笔记
服务器
数据库
后端
前端
工具
《To do list》
日常
阅读
电影
摄影
其他
Software
Windows
WIKIOE
所有分类
所有页面
侧边栏
站点日志
工具
链入页面
相关更改
特殊页面
页面信息