先手动执行如下命令,然后看看效果
>multi
>set str1 "hello world"
>"QUEUED"
>set str2 hello world
>"QUEUED"
>set str2 "welcome"
>"QUEUED"
>exec
> 1) "OK"
> 2) "ERR syntax error"
> 3) "OK"
>get str1
>"hello world"
>get str2
>null
>get str3
>welcome
1)事务以 multi 开始 exec 结束,在exec执行之前的命令,都会存储在Redis服务端的命令队列里面直到exec调用才会执行,因此,任何multi与exec之间的命令都不会及时反馈结果。因此我们无法在事务里面通过读取Redis数据来做决定,这就说明你只能是在multi之前把需要从Redis获取的数据读取出来,然后在到事务里面去做决定,哈哈,这是不是太蠢了,因为这是脏读。
2)从示例结果你可以看出,事务在set str2 发生语法错误,可是整个结果却没有保证数据的原子性(要么成功,要么失败),因为 str1插入成功,str3插入成功,str2插入失败。这说明Redis服务端执行队列里的命令时遇到错误不会回滚也不会停止运行,Redis是这样设计的,理由是回滚是多余的操作,因为错误都是程序员代码造成的,换句话时候代码中不应该出现错误,也就没必要回滚了。总之, 即使事务中有某条/某些命令执行失败了, 事务队列中的其他命令仍然会继续执行 —— Redis 不会停止执行事务中的命令。
3)那么如何解决数据脏读呢?MySQL使用的事务锁机制,很明显高并发下这是不可取的手段,看看Redis是如何处理的,使用 watch(key1,key2...) 命令,在multi之前执行 watch命令,观察监视一个或多个key,如果在接收到exe命令时发现被监视的任何一个key的值有了变化,那么事务就被取消,这里是有原子性的哦,另外,watch 提供的类似CAS(Compare And Set or Check And Set)功能,另watch 其实是乐观锁机制。
4)watch之后,multi之前,这段代码当中可能就会出现值发生变化的时候,那么我们可以在这之间再来做一个判断,判断值是否满足我们预期,如果不满足预期那么就直接 unwatch(exec、discard之后默认执行了unwatch). 然后不执行事务。这其实就是双重锁定,第一层锁放在watch之前,第二层锁放在watch之后multi之前,是不是很熟悉的操作,没错,经典的懒汉式单例模式。
5)没错,unwatch 是希望在执行事务之前就发现数据已经不满足预期,然后手动控制不要去执行事务,那么discard可以在multi至exec之间执行,把事物取消,同样他取消的是整个事务。
6)使用事务的过程中,事务包裹的命令是不会被其他客户端所打扰,另外,底层客户端也会使用流水线来进一步提高事务执行时的性能,换句话说使用事物就默认使用了流水线操作,但是事务的缺点是资源耗费,还可能导致其他重要命令被延迟执行。
7)来看看完整的事务
>set str1 10
>set str2 100
>watch str1
>//伪代码,如果str2的值没有达到101则取消观察,停止程序. if( str2 <= 100 ) unwatch; return;
>multi
>incr str1
>"QUEUED"
>// 伪代码,这里可以在满足某个条件的情况下取消事务的所有命令,discard
>incr str2
>"QUEUED"
>exec
> 1) 11
> 2) 102
来源:oschina
链接:https://my.oschina.net/u/1989321/blog/745081