基本概念
1)什么是redis的事务?
简单理解为命令的集合,一个事务中的所有命令都会序列化,按顺序地串行化执行而不会被其它命令插入,不许加塞,并且有如下两个特点:
a)事务是一个单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。
b)事务是一个原子操作:事务中的命令要么全部被执行,要么全部都不执行。
- redis事务命令
命令 | 描述 |
---|---|
DISCARD | 该命令将会取消事务 |
EXEC | 执行一个事务里面的所有redis命令 |
MULTI | 标记事务的开始 |
UNWATCH | 取消WATCH命令对所有KEY的监视 |
WATCH key [key…] | 监视一个或多个key,如果在事务执行之前,key被改变,将会终止整个事务的执行 |
MULTI
MULTI
: 标记事务的开始,即开启一个事务块命令队列。所有的命令并不会立即执行,而是先存储到一个队列中,等待执行
,例如下面标记事务开始后,使用redis的命令成功后返回QUEUED。
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> set haha 1
QUEUED
127.0.0.1:6379> set k1 v1
QUEUED
127.0.0.1:6379> get haha
QUEUED
127.0.0.1:6379> hset cache::num userId 123456
QUEUED
EXEC
EXEC
:执行队列里所有的命令,整个过程是批处理且原子性操作,要么全部成功,要么全部失败。
// 全部成功
127.0.0.1:6379> EXEC
1) OK
2) OK
3) "1"
4) (integer) 1
上面的命令因为执行都是正确,当执行EXEC时候,全部返回成功的案列,下面举一个失败的案列。
// 全部失败的案列
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> set haha 2
QUEUED
127.0.0.1:6379> set k3 v3
QUEUED
127.0.0.1:6379> set xixi
(error) ERR wrong number of arguments for 'set' command
127.0.0.1:6379> EXEC
(error) EXECABORT Transaction discarded because of previous errors.
// 获取k3的值
127.0.0.1:6379> get k3
(nil)
在上面的新开启标记事务时,特意写入set xixi ,但是没有给这个xixi这个key进行赋值,显然这个命令是错误的,当我们获取上面命令的执行结果如获取k3的值,发现set k3 v3
这个命令并没有成功,其他的一些key操作也是如此,所以我们得出结论:如果命令有一条错误的,则整个队列的命令数据都会失败
。
那么如果是下面的案列呢,请注意以下的命令
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> set haha 1
QUEUED
127.0.0.1:6379> hincrby cache::schema userid 123
QUEUED
127.0.0.1:6379> set java spring
QUEUED
127.0.0.1:6379> incr java
QUEUED
上面最后一个命令,很显然java这个key对应的value值并不是数值类型,是不是当执行这个队列任务时候,也是全部失败呢?那我们执行一个EXEC命令,结果如下:
127.0.0.1:6379> EXEC
1) OK
2) (integer) 123
3) OK
4) (error) ERR value is not an integer or out of range
居然有3条命令成功了!!!!,而第四条命令是失败的,不是说好的如果一个队列中,有一个命令是错误的,那么整个队列的命令都将会失败。
搞清楚这个问题之前,做个java开发的都知道,我们在写代码的时候,进行IO流的操作的时候,经常不得不手动将IOE EXCEPTION
给处理了,否则代码编译都通不过。所以在上面全部失败的案列中
,set xixi
这个命令可理解为预编译的异常。所以set xixi还没有运行就直接返回异常了。而我们使用的命令incr java
这个命令,只有在执行的时候,才会报错,这点类似java平常中的RuntimeException
一样。
DISCARD
- DISCARD:取消事务,整个队列里的所有redis命令不会执行。
// 标记事务开始
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> set haha 2
QUEUED
127.0.0.1:6379> set xixi 3
QUEUED
127.0.0.1:6379> get haha
QUEUED
127.0.0.1:6379> DISCARD
OK
127.0.0.1:6379> get haha
"1"
通过上面命令我们发现haha的值并不是等于2而还是之前的值1,这是因为我们将加入队列中的命令进行了取消。
WATCH
解释WATCH之前,让我们举个小小的例子。
假设小明的账户上有80元钱,小明开启事务并花了20元,如下命令所示:
127.0.0.1:6379> watch money
OK
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> decrby money 30
QUEUED
而此时小明的女朋友给小明打了30元,她进行下面的命令操作:
127.0.0.1:6379> get money
"80"
127.0.0.1:6379> incrby money 30
OK
此时小明执行EXEC,并将要进行账户扣款操作如下命令所示:
127.0.0.1:6379> EXEC
(nil)
然而确没有成功,这是因为我们监控了小明的账户,而在监控过程中,小明的事务还没有执行的时候,小红进行了转账操作,导致小明账户扣款异常。这是一个典型的乐观锁思想:
watch的作用是:在事务提交的时候如果有其他客户端对key进行了操作和改变,那么整个事务队列的命令不会执行。即EXEC命令执行的事务中命令无效。
总结
开启:以MULTI开始一个事务
入队:将多个命令入队到事务中,接到这些命令并不会立即执行,而是放到等待执行的事务队列里面
执行:由EXEC命令触发事务
redis事务并不像关系型数据库中那样,如果有一条记录失败,则会整个事务过程其他操作也都失败,也就是redis的事务并没有满足原子性。redis的事务也没有关系型数据库隔离性的概念,这是因为redis的事务提交前,队列中的命令都没有进行执行,所以也就不存在事务内的查询要看到事务里的更新,在事务外查询不能看到