Java充电社
专辑
博文
联系我
本人继续续收门徒,亲手指导
Redis教程
-> 第7篇:Redis事务操作
1、第1篇:Redis概述和安装
2、第2篇:Redis 5大数据类型
3、第3篇:Redis的发布和订阅
4、第4篇:Redis新的3种数据类型
5、第5篇:Jedis操作Redis6
6、第6篇:SpringBoot2整合Redis
7、第7篇:Redis事务操作
8、第8篇:Redis持久化之RDB(Redis DataBase)
9、第9篇:Redis持久化之AOF(Append Only File)
10、第10篇:Redis主从复制
11、第11篇:Redis集群(Cluster)
12、第12篇:Redis应用问题解决(缓存穿透、击穿、雪崩、分布式锁)
上一篇:第6篇:SpringBoot2整合Redis
下一篇:第8篇:Redis持久化之RDB(Redis DataBase)
<div style="display:none"></div> ## 7.1、redis事务定义 redis事务是一个单独的隔离操作,事务中的所有命令都会序列化、按顺序地执行,事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。 redis事务的主要作用就是串联多个命令防止 别的命令插队。 ## 7.2、Multi、Exec、discard 从输入Multi命令开始,输入的命令都会依次进入命令队列中,但不会执行,直到输入Exec后,redis会将之前的命令依次执行。 组队的过程中可以通过discard来放弃组队。 **redis事务分2个阶段:组队阶段、执行阶段** - **组队阶段**:只是将所有命令加入命令队列 - **执行阶段**:依次执行队列中的命令,在执行这些命令的过程中,不会被其他客户端发送的请求命令插队或者打断。 ![](https://itsoku.oss-cn-hangzhou.aliyuncs.com/itsoku/blog/article/254/1eaf8654-393e-4883-b67b-77f75cd20b6a.png) ## 7.2.1、相关的几个命令 #### multi:标记一个事务块的开始 标记一个事务块的开始。 事务块内的多条命令会按照先后顺序被放进一个队列当中,最后由 exec 命令原子性(atomic)地执行。 **示例** ```shell redis> MULTI # 标记事务开始 OK redis> INCR user_id # 多条命令按顺序入队,返回值为QUEUED,表示这个命令加入队列了,还没有被执行。 QUEUED redis> INCR user_id QUEUED redis> INCR user_id QUEUED redis> PING QUEUED redis> EXEC # 执行 1) (integer) 1 2) (integer) 2 3) (integer) 3 4) PONG ``` #### exec:执行所有事务块内的命令 执行所有事务块内的命令。 假如某个(或某些) key 正处于`watch`命令的监视之下,且事务块中有和这个(或这些) key 相关的命令,那么`exec`命令只在这个(或这些) key 没有被其他命令所改动的情况下执行并生效,否则该事务被打断(abort)。 **返回值:** 事务块内所有命令的返回值,按命令执行的先后顺序排列。 当操作被打断时,返回空值 `nil` 。 **3个示例** ```shell redis> MULTI OK redis> INCR user_id QUEUED redis> INCR user_id QUEUED redis> INCR user_id QUEUED redis> PING QUEUED redis> EXEC 1) (integer) 1 2) (integer) 2 3) (integer) 3 4) PONG redis> WATCH lock lock_times OK redis> MULTI OK redis> SET lock "huangz" QUEUED redis> INCR lock_times QUEUED redis> EXEC 1) OK 2) (integer) 1 redis> WATCH lock lock_times OK redis> MULTI OK redis> SET lock "joe" # 就在这时,另一个客户端修改了 lock_times 的值 QUEUED redis> INCR lock_times QUEUED redis> EXEC # 因为 lock_times 被修改, joe 的事务执行失败 (nil) ``` #### discard:取消事务 取消事务,放弃执行事务块内的所有命令。 **返回值:** 总是返回 `OK` 。 **示例** ```shell redis> MULTI OK redis> PING QUEUED redis> SET greeting "hello" QUEUED redis> DISCARD OK ``` ## 7.3、事务的错误处理 ### 7.3.1、情况1:组队中命令有误,导致所有命令取消执行 组队中某个命令出现了错误报告,执行时整个队列中所有的命令都会被取消。 ![](https://itsoku.oss-cn-hangzhou.aliyuncs.com/itsoku/blog/article/254/83f4bc64-7e97-4c12-a9ba-46b51461703c.png) 示例代码如下,事务中执行了3个set命令,而第3个命令`set address`命令本身有问题,加入队列失败,最后执行exec的时候,所有的命令都被取消执行。 ```shell 127.0.0.1:6379> flushdb OK 127.0.0.1:6379> multi #开启一个事务块 OK 127.0.0.1:6379(TX)> set name ready QUEUED 127.0.0.1:6379(TX)> set age 30 QUEUED 127.0.0.1:6379(TX)> set address #命令有问题,导致加入队列失败 (error) ERR wrong number of arguments for 'set' command 127.0.0.1:6379(TX)> exec #执行exec的时候,事务中所有命令都被取消 (error) EXECABORT Transaction discarded because of previous errors. ``` ### 7.3.2、情况2:组队中没有问题,执行中部分成功部分失败 命令组队的过程中没有问题,执行中出现了错误会导致部分成功部分失败。 ![](https://itsoku.oss-cn-hangzhou.aliyuncs.com/itsoku/blog/article/254/59b4d3ea-94dd-4760-bf35-325e08fa8de2.png) 示例代码如下,事务中有3个命令,3个命令都入队列成功了,执行exec命令的时候,1和3命令成功了,第2个失败了 ```shell 127.0.0.1:6379> multi OK 127.0.0.1:6379(TX)> set k1 v1 #命令1:设置k1的值为v1 QUEUED 127.0.0.1:6379(TX)> incr k1 #命令2:k1的值递增1,由于k1的值不是数字,执行的时候会失败的 QUEUED 127.0.0.1:6379(TX)> set k2 v2 #命令3:设置k2的值为v2 QUEUED 127.0.0.1:6379(TX)> exec #执行命令,1和3命令成功,第2个失败了 1) OK 2) (error) ERR value is not an integer or out of range 3) OK 127.0.0.1:6379> mget k1 k2 #查看k1和k2的值 1) "v1" 2) "v2" ``` ## 7.4、事务冲突的问题 #### 7.4.1、例子 **想象一个场景:** 你的账户中只有10000,有多个人使用你的账户,同时去参加双十一抢购 一个请求想给金额减8000 一个请求想给金额减5000 一个请求想给金额减1000 ![](https://itsoku.oss-cn-hangzhou.aliyuncs.com/itsoku/blog/article/254/0804cde9-645f-4bec-96ea-f379279e8aa1.png) 3个请求同时来带①,看到的余额都是10000,大于操作金额,都去执行修改余额的操作,最后导致金额变成了-4000,这显然是有问题的。 #### 7.4.2、悲观锁 ![](https://itsoku.oss-cn-hangzhou.aliyuncs.com/itsoku/blog/article/254/b33a4ed4-5d78-44dd-a515-2f68711983b7.png) 悲观锁(Pessimistic Lock),顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人拿到这个数据就会block直到它拿到锁。传统的关系型数据库里面就用到了很多这种锁机制,比如行锁、表锁、读锁、写锁等,都是在做操作之前先上锁。 #### 7.4.3、乐观锁 ![](https://itsoku.oss-cn-hangzhou.aliyuncs.com/itsoku/blog/article/254/6bd257fc-eea7-4d49-8142-22cc0902c7b3.png) 乐观锁(Optimistic Lock),顾名思义,就是很乐观,每次去那数据的时候都认为别人不会修改,所以不会上锁,但是在修改的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量。redis就是使用这种check-and-set机制实现事务的。 ### 7.4.4、watch key [key ...] 在执行multi之前,先执行watch key1 [key2 ...],可以监视一个或者多个key,若在事务的exec命令之前这些key对应的值被其他命令所改动了,那么事务中所有命令都将被打断,即事务所有操作将被取消执行。 **示例** 开启2个窗口,按照下表的时间点在不同的窗口执行对应的命令,注意看结果。 | 时刻 | 窗口1 | 窗口2 | | ---- | ----------------- | ----------------- | | T1 | flushdb | | | T2 | set balance 100 | | | T3 | watch balance | | | T4 | multi | | | T5 | set name ready | incrby balance 50 | | T6 | incrby balance 10 | get balance | | T7 | exec | | | T8 | get balance | | | T9 | get name | | 窗口1中,对balance进行了监视,也就是说在执行`watch balance`命令之后,在`exec`命令之前,如果有其他请求对balance进行了修改,那么窗口1事务中所有的命令都会将会被取消执行。 窗口1`watch balance`后,由于T5时刻窗口2对balance进行了修改,导致窗口1中事务所有命令被取消执行。 窗口1执行结果如下 ```shell 127.0.0.1:6379> flushdb #清空db,方便测试 OK 127.0.0.1:6379> set balance 100 #设置balance的值为100 OK 127.0.0.1:6379> watch balance #监视balance,若balance在事务阶段被其他命令修改,事务执行将被取消 OK 127.0.0.1:6379> multi #开启事务 OK 127.0.0.1:6379(TX)> set name ready #设置name的值为ready QUEUED 127.0.0.1:6379(TX)> incrby balance 10 #将balance的值+10 QUEUED 127.0.0.1:6379(TX)> exec #执行事务,由于balance被窗口2修改了,所以本事务执行失败,返回nil (nil) 127.0.0.1:6379> get balance #获取balance,原始值为100,被窗口2加了50,结果为150 "150" 127.0.0.1:6379> get name #获取name的值,事务中set name 未成功,所以name没有 (nil) ``` 窗口2执行结果 ```shell 127.0.0.1:6379> incrby balance 50 #balance原子+50 (integer) 150 127.0.0.1:6379> get balance #获取balance的值,为150 "150" ``` ### 7.4.5、unwatch:取消监视 取消 WATCH 命令对所有 key 的监视。 如果在执行 WATCH 命令之后, EXEC 命令或 DISCARD 命令先被执行了的话,那么就不需要再执行 UNWATCH 了。 因为 EXEC 命令会执行事务,因此 WATCH 命令的效果已经产生了;而 DISCARD 命令在取消事务的同时也会取消所有对 key 的监视,因此这两个命令执行之后,就没有必要执行 UNWATCH 了。 ```shell redis> WATCH lock lock_times OK redis> UNWATCH OK ``` ## 7.5、redis事务三特性 ### (1)单独的隔离操作 事务中的所有命令都会序列化、按顺序地执行,事务在执行过程中,不会被其他客户端发送来的命令请求所打断。 ### (2)没有隔离级别的概念 队列中的命令没有提交(exec)之前,都不会实际被执行,因为事务提交前任何指令都不会被实际执行。 ### (3)不能保证原子性 事务中如果有一条命令执行失败,后续的命令仍然会被执行,没有回滚。 如果在组队阶段,有1个失败了,后面都不会成功;如果在组队阶段成功了,在执行阶段有那个命令失败就这条失败,其他的命令则正常执行,不保证都成功或都失败。 <a style="display:none" target="_blank" href="https://mp.weixin.qq.com/s/_S1DD2JADnXvpexxaBwLLg" style="color:red; font-size:20px; font-weight:bold">继续收门徒,亲手带,月薪 4W 以下的可以来找我</a> ## 最新资料 1. <a href="https://mp.weixin.qq.com/s?__biz=MzkzOTI3Nzc0Mg==&mid=2247484964&idx=2&sn=c81bce2f26015ee0f9632ddc6c67df03&scene=21#wechat_redirect" target="_blank">尚硅谷 Java 学科全套教程(总 207.77GB)</a> 2. <a href="https://mp.weixin.qq.com/s?__biz=MzkwOTAyMTY2NA==&mid=2247484192&idx=1&sn=505f2faaa4cc911f553850667749bcbb&scene=21#wechat_redirect" target="_blank">2021 最新版 Java 微服务学习线路图 + 视频</a> 3. <a href="https://mp.weixin.qq.com/s?__biz=MzkwOTAyMTY2NA==&mid=2247484573&idx=1&sn=7f3d83892186c16c57bc0b99f03f1ffd&scene=21#wechat_redirect" target="_blank">阿里技术大佬整理的《Spring 学习笔记.pdf》</a> 4. <a href="https://mp.weixin.qq.com/s?__biz=MzkwOTAyMTY2NA==&mid=2247484544&idx=2&sn=c1dfe907cfaa5b9ae8e66fc247ccbe84&scene=21#wechat_redirect" target="_blank">阿里大佬的《MySQL 学习笔记高清.pdf》</a> 5. <a href="https://mp.weixin.qq.com/s?__biz=MzkwOTAyMTY2NA==&mid=2247485167&idx=1&sn=48d75c8e93e748235a3547f34921dfb7&scene=21#wechat_redirect" target="_blank">2021 版 java 高并发常见面试题汇总.pdf</a> 6. <a href="https://mp.weixin.qq.com/s?__biz=MzkwOTAyMTY2NA==&mid=2247485664&idx=1&sn=435f9f515a8f881642820d7790ad20ce&scene=21#wechat_redirect" target="_blank">Idea 快捷键大全.pdf</a> ![](https://itsoku.oss-cn-hangzhou.aliyuncs.com/itsoku/blog/article/1/2883e86e-3eff-404a-8943-0066e5e2b454.png)
#custom-toc-container