1、事务是什么
可以一次执行多个命令,本质是一组命令的集合,一个事务中的所有命令都会序列化,按顺序地串行化执行而不会被其他命令插入,不许加塞。
2、能干啥
一个队列中,一次性、顺序性、排他性的执行一系列命令。
3、怎么玩
3.1 常用命令
Multi:标记一个事务的开始;
Exec:执行事务块内的所有命令;
Discard :取消事务,放弃执行事务块内的所有命令;
Unwatch:取消watch命令对所有key的监听;
Watch key [key…]:监听一个或多个key,如果在事务执行之前这些key被其他命令打断,那么事务将被打断。
Case1:正常执行:
在执行完 multi命令后,开启事务,之后的所有命令都是放入到队列中的,执行exec命令时提交事务,此时执行队列中的各个命令,并把结果返回。
Case2 :放弃事务:
也就是在执行multi命令开启事务后,执行一部分操作后,放弃该事务,执行discard命令即可。我们也可以看到后面在执行exec提交事务是会报异常的。这种情况怎么办呢?
Watch key后,对key的value进行了修改,只能unwatch,然后在watch。想想我们通过version控制数据库的乐观锁也是一样。
Case3:事务的原子性(要么都成功,要么都失败)部分支持:
1)、执行命令报错,未加入队列,全体回滚:
开启事务后,先执行一条插入语句,set k1 v1 没问题,紧跟着执行set k2 不符合redis的语法直接报错,后面在执行exec提交事务操作时直接报错,导致整个事务块的内容都没提交; 原因就是set k2 这条命令未加入队列中报错,导致事务全部回滚。
2)、执行命令不报错,已经加入队列,
但是redis内部执行报错,此时只有那一条命令不成功,其余均成功;
看下上面的命令,也是开启一个事务,首先是set k1 v1 成功执行,紧跟着执行incr k1 把v1的值加1,我们知道k1对应的值为字符v1 ,不能加1,但是这个命令incr k1还是成功的放入队列中了,之后我们又把set k2 v2,执行提交事务操作。我们看到k1 和k2 都已经成功插入到redis中,唯有incr k1 没成功执行,这就是redis的事务的部分支持。
Case4:watch监控
悲观锁:每次去拿数据时会认为别人会去修改数据,所以每次去拿数据的时候都会上锁,这样别人拿数据就会block直到到释放锁,传统的数据库就用到很多这样的锁,比如行锁,表锁,读锁,写锁都是在操作前先上锁。
乐观锁:总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号机制和CAS算法实现。乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库提供的类似于write_condition机制,其实都是提供的乐观锁。在Java中java.util.concurrent.atomic包下面的原子变量类就是使用了乐观锁的一种实现方式CAS实现的。
Watch指令类似与乐观锁,事务提交时,如果某个key的值已经被别的客户端改变,比如某个list已被别的客户端push/pop过了,整个事务队列都不会被执行。
通过watch命令在事务执行之前监控多个keys,倘若在watch之后有任务key的值发生了变化,exec命令执行的事务都被放弃,同时返回Nullmulti-bulk应答以通知调用者事务执行失败。
同一线程操作且未改变值。
在同一个线程内,首先对balance加锁(这时候其他线程对balance也可以进行任何其他操作),然后开启一个事务,对balance减20,debt加20操作,最后提交事务,可以看到正确执行。
同一线程操作但值已经发生改变
在同一个线程中,watch balance的值后,又对其进行了修改,这时候开启事务并对balance和debt的值进行修改,我们可以发现提交事务还是报错,这是因为watch balance时记录的balance对应的值是500;后面把它的值改为200;而在提交事务时是把redis中的balance对应的当前值与watch时的值比较的,不一样就不运行提交事务。
A线程加watch key,B线程修改key的值
A线程对balance加watch,然后开启事务,对balance和debt的值分别进行了减、增20,此时还没提交事务,线程B这时候对balance进行了更改操作,那么我们在线程a内提交事务会报错。
这和db2数据库通过version字段对每行数据加锁机制是类似的:在操作前先查询下version的值,提交事务前在查询一次,两者一致这提交事务否则报错。这里也是一样,先监控balance的值,提交事务前在查询下与watch时的值是否一致,不一致就不能提交事务。
6.4 阶段
开启:以multi开始一个事务;
入队:将多个命令入队到事务中,接到这些命令并不会立即执行,而是放到等待执行的事务队列里面;
执行:由exec命令触发事务。
6.5 特性
单独的隔离操作:事务中的所有命令都会序列化,按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断;
没有隔离级别的概念:队列中的命令没有提交之前都不会实际的被执行,因为事务的提交前任何指令都不会被实际执行; 也就不存在“事务内的查询要看到事务里的更新,在事务外查询不能看到”这个让人万分头疼的问题;
不保证原子性:redis同一个事务中如果有一条命令执行失败,如是redis内部的失败,命令本身入队,则其他命令都会成功执行,若命令本身执行就报错,压根就没有如队列,那么事务内的全部命令都回滚。