赞
踩
熟悉关系型数据库,例如MySQL的读者,应该对事务是比较了解的,并且向MySQL这样的事务机制可谓是十分强大!
所谓事务,说白了就是一些命令的组合,这一组命令要么全部执行完毕,要么全部不执行,不会存在执行到一半就结束的状态;举个简单的例子:甲向乙进行转账5元,对应的组合命令如下:
这一组命令集合就是事务,对于这一批命令最后要么是全部都执行,那么就是甲的余额减少5元,乙的余额增加5元;要么全部不执行,甲、乙的余额都没发生变化;不会出现甲这边减少了5元,但是乙这边余额没有增加5元的情况;这就是事务!同时对于事务这种要么全部执行,要么全部不执行的特性叫做原子性!
在MySQL中对于事务原子性的支持就非常的完美,对于MySQL的事务来说,如果在事务的处理过程中出现了错误,那么MySQL会终止这个事务,然后对这个事务已经进行操作进行回滚!将数据恢复成事务没有开始执行前的样子;但是对于redis中的事务来说,它对于原子性的操作就支持的并不是特别好,因为在redis的事务中,即使事务在执行过程中出现了错误,那么redis不会进行回滚,而是会继续向后执行后续的操作。
至于redis的原子性为什么不带上回滚?
大概与redis高性能的应用场景有关,如果redis设计了回滚操作的话,那么redis势必会花费更多的“精力”来维护回滚操作,可能也会引入类似于事务ID、回滚指针、undolog日志等模块,这势必会复杂化redis的业务逻辑,同时这些模块的数据也是需要内存来进行存储的,这样的话redis能够存储的有效数据就变少了,综上这些,可能是redis事务的原子性不带上回滚的原因。
- 原子性: 如果按照MySQL中的原子性来定义的话。那么redis事务的原子性是不存在的,因为对于redis事务来说执行过程中出现错误不会进行回滚,而是继续向后执行。
- 一致性:redis事务不具备一致性,因为redis事务在执行过程中出现错误不会进行回滚,那么最后事务结束运行出来的结果是不可预期的,一致性没有得到保证。
- 持久性:redis事务不具备持久性,因为redis本身就是内存级别的数据库,数据存储在内存中,即使redis拥有持久化机制,但是这个持久化机制并不是强制的,可以由用户手动关闭,与事务是两个独立的模块,并没有强相关起来,而对于MySQL事务的持久性来说,则是强制进行的,用户无法干预;
- 隔离性:redis事务不具备隔离性,因为存在隔离性的条件首先是并发,但是对于redis来说,它本身就是用单线程来处理事务,事务与事务之间的执行过程都是串行的,因此不存在数据不一致的情况,故redis事务不具备隔离性。
上面我们讲到了事务的原子性、一致性、持久性、隔离性,接下来我们来讲一讲在redis事务中如何开启一个事务:
在redis中操作事务的指令如下:
- mutli命令:开启事务
- exec命令:提交事务
- discard命令:放弃事务
下面我们来具体操作一个事务看看:
我们就以转账为例,现在有两角色:Tom、Bob ,Tom的卡内余额为15元,Bob的卡内余额为5元,现在我们要求Tom向Bob转账5元,那么最后的结果就是Tom的卡内余额为10元,Bob的卡内余额为10元;
- 使用multi开启事务:
- 然后在事务中进行转账逻辑的书写:
经过书写转账逻辑我们发现,decrby Tom 5和incrby Bob 5的返回值都是QUEUED,而不是向平常一样返回命令执行过后的结果,这是因为此时的事务还没有提交,只有我们再次输入exec命令完成事务的提交过后,才能够执行上述的逻辑?
那么在事务提交之前,这些命令存储在哪里?
答: 当客户端和redis服务器进行连接过后,redis服务器会给每个客户端都维护一个队列,这个队列就用来专门存储客户端未提交的事务的命令,当客户端输入exec事务提交命令过后,redis服务器才会将这个队列里面的命令全部取出来一次性执行,并且在执行的过后中,就算有其它客户端的命令到来了,也无法“插入”到这批命令集合中进行执行,只有当这一批命令执行完毕过后,才会去处理下一个客户端的命令。当然了我们如果输入discard命令的话,那么redis服务器会放弃队列中的已经存储好的命令,那么这个事务自然也就被放弃了;
- 使用exec命令提交事务,再来观察结果:
通过提交事务过后,我们发现最终Tom的卡内余额变为了10,Bob的卡内余额也变为了10,没有出现Tom卡内余额减少,但是Bob卡内余额不变的情况;
当然,如果我们书写完转账逻辑过后,我们又不想进行转账操作了,那么我们完全可以使用discard命令来放弃本次事务,放弃事务的本质也就是:放弃redis服务器内队列中的命令:
经过实验,我们发现当我们放弃事务过后,数据库中的数据都没有发生变化。
当然,如果事务中的命令出现了错误,redis的处理机制也不进相同:
- 命令错误
像上述这种,在事务中存在语法错误的,客户端检查出来了会给你进行报警,但是还是会将你的命令发送到服务端的队列中进行存储;当我们执行exec命令过后,服务端会从队列中取出所有命令,并且会进行一遍语法检查,如果检查出语法错误,那么这个事务会被放弃,这点可以看出redis还是在进自己的最大可能来保证事务的一致性。- 运行时错误
像上述那种,并没有明显的语法错误,但是存在着逻辑错误,只有当事务被提交,即事务运行起来过后才会发现,但是这时候redis事务即使发现了错误也不会撤销在错误之前所做出的修改,而是一股脑的往后执行,这样的错误其实与redis无关,之所以会出现这样的错误完全是由于我们开发人员写错了redis命令造成的,如果我们正确书写了redis语句,那么redis的事务的原子性是不是就与MySQL的事务的一样了呢?
有些应用场景需要在事务之前,确保事务中的key没有被其它客户端修改过,才执行事务,否则不执行。redis提供了watch命令来解决此类问题,下面我们来看一看两个客户端执行命令的时序:
从时间上来看,客户端1的set命令先到达服务器,而客户端2的命令后到达服务器,最后的结果应该是key1 222,但是实际情况是,虽然客户端1的命令先到达服务器但是由于客户端1发送的是一个事务,只有当事务被真正提交了,才会被执行,而这个提交命令又是晚于客户端2到达服务器的,因此最后的结果就是:key1 111;
像上述这种,在我事务还没有进行提交之前,事务里面的key就被客户端2修改的情况,我们希望,客户端1能够不执行这个事务,想要达到这样的效果,我们可以用watch来监视一下事务中的key:
具体操作如下:
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。