redis事务,watch、multi、exec以及discard命令的使用
发布于 作者:苏南大叔 来源:程序如此灵动~类似于mysql
的事务机制,redis
也有事务机制。mysql
中的select ... for update
是一种悲观锁机制。而redis
中的watch
命令为代表的事务,是个乐观锁机制。本文的重要结论就是:redis
的事务【不能回滚】!只能【中断或者跳过】执行!接下来就看看在redis
事务中,watch
(unwatch
)、multi
、exec
以及discard
这几条重要组成命令是如何使用的吧!
苏南大叔的“程序如此灵动”博客,记录苏南大叔的代码编程故事。本文测试环境:win10
,redis@5.0.14.1
。
前文回顾
redis
下载地址:
- 【官方不支持win】 https://redis.io/download/
- 【非官方版本低】 https://github.com/tporadowski/redis/releases
redis
官方没有提供windows
版本的redis
程序,官方建议在windows
系统下的wsl
虚拟机中使用redis
。参考文章:
- https://newsn.net/say/wsl-redis.html
- https://newsn.net/say/wsl-redis-2.html
- https://newsn.net/say/redis-win.html
本文出于测试的目的,使用的是非官方绿色版的redis@5.0.14.1
软件。如果不习惯命令行界面,还可以安装redisdesktop
软件。参考文章:
启动进程
默认端口6379
启动redis-server.exe
,然后启动了两个redis-client.exe
,用于模拟事务相关命令的使用环境。
- 第一个
redis-client.exe
客户端,用于发起事务。 - 第二个
redis-client.exe
客户端,用于破坏线程一的redis
事务。
redis 事务
事务其实就是一系列命令的组合,理论上来说是要不全部执行,要不全部不执行。而redis
事务并不是完整概念上的事务,它没有回滚机制,会出现“一部分执行,某条执行失败,然后后续的命令再执行”的情况。也就是说:redis
事务不具有原子性。或者说redis
的事务实际上是个命令组合功能,并不是真正的事务。
redis
事务命令,一共有以下几个命令所组成。
命令 | 用途 | 解释 |
---|---|---|
multi | 组装事务 | 类似于begin 命令 |
exec | 执行事务 | 类似于commit 命令 |
discard | 取消事务 | 注意不是回滚 |
watch | 监视【一些】key | 一旦这些key 在事务执行(exec )之前被改变,则取消事务的执行。 |
unwatch | 取消所有被watch 的变量效果 | 没参数,取消所有变量 |
redis 事务之打包
先看个普通的redis
事务功能:
set age 1
multi
set age 2
exec
get age
或者:
set age 1
multi
set age 2
discard
get age
multi
开始一个事务,exec
或者discard
结束一个事务。或者由其它进程通过改变被watch
的变量的方式,来被动discard
。redis
事务不支持回滚!只支持取消!
redis 事务之 watch
事务不一定要配合watch
命令的。不执行watch
命令的话,就是个普通的打包功能。watch
是提供一个其它进程取消本线程事务的功能。redis
在事务开始之前,可以先watch
一个或几个变量。如果这些变量被其它进程改变了,就拒绝线程里对应事务全部命令的执行!
再解释一次:当前redis
事务的执行,是基于被watch
的某个或某几个key
的不变性的。只有被watch
的key
不变,对应事务才能执行!否则就是被动的discard
。而key
是可以被其它线程改变的!看,就是这么的“舍小家为大家、大公无私、充满爱”的操作!
被watch
的变量,如果在对应的事务里面被修改,因为set key value
是个队列,所以理论上来说,并不是真正的被修改。所以,一个redis
事务在watch
某个key
的时候,又在内部修改对应key
。并不是个悖论,是不发生冲突的。
例子一 watch
序号 | 线程一 | 线程二 |
---|---|---|
0 | watch age | 无 |
1 | multi | 无 |
2 | 无 | set age 888 |
3 | set age 999 | 无 |
4 | exec | 无 |
5 | get age | get age |
最后的age
取值是888
,因为线程二通过设置修改age
变量已经破坏了线程一的事务。(线程一的事务已经被隐式的discard
了)
例子二 watch
线程watch
多个key
也是可以的,任何一个key
被其它线程修改的话,事务就会被隐式的discard
。
序号 | 线程一 | 线程二 |
---|---|---|
0 | watch age location | 无 |
1 | multi | 无 |
2 | 无 | set location peking |
3 | set age 999 | 无 |
3 | set location Seoul | 无 |
4 | exec | 无 |
5 | get age | get age |
5 | get location | get location |
这个例子里面age
取值是888
或者(nil)
,location
取值是peking
。
例子三 unwatch
watch
和unwatch
只对当前线程有效,不影响其它线程。
序号 | 线程一 | 线程二 |
---|---|---|
0 | watch age | 无 |
1 | unwatch | 无 |
2 | multi | 无 |
3 | 无 | set age 888 |
4 | set age 999 | 无 |
5 | exec | 无 |
6 | get age | get age |
序号 | 线程一 | 线程二 |
---|---|---|
0 | watch age | 无 |
1 | multi | 无 |
2 | 无 | unwatch |
3 | 无 | set age 888 |
3 | set age 999 | 无 |
4 | exec | 无 |
5 | get age | get age |
redis 事务之“防插队”
网上其它教程里面说redis
事务最大的特点就是“防止插队”。苏南大叔对于这句话是这么理解的:
- 事务内部保证顺序执行,事务内部防止插队。
- 不同线程的两个事务之间,是没有先来后到的说法的。没有谁先
multi
谁就会被先执行的说法。谁先执行只取决于谁先exec
。
结语
相比较mysql
事务的悲观锁,redis
的事务是个乐观锁。就是说,本线程执行自己的线程,要是有其它人破坏了规则,那本线程就不执行了。很乐观很大方吧?
苏南大叔的更多redis
经验文章,请点击下面的链接:
本博客不欢迎:各种镜像采集行为。请尊重原创文章内容,转载请保留作者链接。