我们知道Redis是支持事务的,但是它里面的事务为什么不支持回滚呢?
1.在Redis中,命令只会因为错误的语法而失败,或者是命令用在了错误类型的键上面;
也就是说,从实用的角度说,失败的命令是由编译错误造成的,而这些错误应该在开发过程中被发现,而不应该出现在生产环境中。
2.因为不需要对回滚进行支持,所以Redis的内部可以保持简单且快速。
3.有种观点认为,Redis处理事务的做法会产生bug,但是需要注意的是,通常情况下,回滚并不能解决编程错误而带来的问题。
例如,如果你本来想通过INCR命令将键的值加1,却不小心加了2,又或者对错误类型的键执行了INCR,回滚是没办法处理这些情况的。
鉴于没有任何机制能避免程序员自己造成的错误,并且这类错误通常不会在生产环境出现,所以Redis选择了更简单、更快速的无回滚方式处理事务。
我们都知道,事务有 4 大特性。分别是:原子性(Atomicity)
、一致性(Consistency)
、隔离性(Isolation)
、持久性(Durability)
。
原子性(Atomicity)
原子性
是指事务是一个不可分割的工作单位,事务中的操作要么全部成功,要么全部失败。比如在同一个事务中的 SQL 语句,要么全部执行成功,要么全部执行失败。
然而,Redis 中的事务,如果在执行中间失败了,在事务开始之前到遇到命令执行失败这中间执行的命令不会回滚。
这就导致了,Redis 的事务没有保证原子性。
下面看一个例子:
redis 127.0.0.1:7000> multi
OK
redis 127.0.0.1:7000> set a aaa
QUEUED
redis 127.0.0.1:7000> set b bbb
QUEUED
redis 127.0.0.1:7000> set c ccc
QUEUED
redis 127.0.0.1:7000> hhh www.xttblog.com
QUEUED
redis 127.0.0.1:7000> exec
1) OK
2) OK
3) OK
4)-ERR Operation against a key holding the wrong kind of value
虽然上面这段命令执行过程中会遇到错误,但是不会回滚。
set a、set b 等命令操作执行成功了。可以通过 get 取到对应的值。具体我就不贴代码了。
Redis 执行事务过程
Redis 客户端提供了管道操作。
管道可以理解为一个打包的批量执行脚本,但批量指令并非原子化的操作;
中间某条指令的失败不会导致前面已做指令的回滚,也不会造成后续的指令不做。
一致性(Consistency)
Redis 事务的命令主要是 multi(开启事务) exec(执行事务) discard(丢弃事务)。
Redis 事务在执行的过程中,不会处理其它命令,而是等所有命令都执行完后,再处理其它命令。
因此在 Redis 事务在执行过程中发生错误或进程被终结,都能保证数据的一致性。
隔离性(Isolation)
前面也说了,Redis 的事务在执行的过程中,不会处理其它命令,而是等所有命令都执行完后,再处理其它命令。
不管用不用管道,只要执行了 multi,就会阻塞其他操作。因此 Redis 事务是满足隔离性的。
Redis的事务没有隔离级别
况且 Redis 是一个单线程的。
另外需要注意的是:Redis 虽然保证了隔离性,但是它对事务没有隔离级别的概念,
所以就不会产生我们使用关系型数据库需要关注的脏读,幻读,重复读的问题
。
持久性(Durability)
这个特性可谈可不谈,因为大部分情况下,Redis 是用来做缓存的。很多公司是没有做持久化的,因此可以说 Redis 事务的持久性是不支持的。
Redis 事务不过是用队列包裹起了一组 Redis 命令,并没有提供任何额外的持久性功能,
所以事务的持久性由 Redis 所使用的持久化模式决定:
- 在单纯的内存模式下,事务肯定是不持久的。
- 在 RDB 模式下,服务器可能在事务执行之后、RDB 文件更新之前的这段时间失败,所以 RDB 模式下的 Redis 事务也是不持久的。
- 在 AOF 的
总是 SYNC
模式下,事务的每条命令在执行成功之后,都会立即调用 fsync 或 fdatasync 将事务数据写入到 AOF 文件。但是,这种保存是由后台线程进行的,主线程不会阻塞直到保存成功,所以从命令执行成功到数据保存到硬盘之间,还是有一段非常小的间隔,所以这种模式下的事务也是不持久的。 - 其他 AOF 模式也和
总是 SYNC
模式类似,所以它们都是不持久的。
因此,我们可以说 Redis 的事务是不支持持久化的,或者说持久化是有缺陷的。就像 Redis 的分布式锁一样。
watch 机制实现乐观锁
虽说 Redis 不支持直接回滚,但我们可以通过 Redis 提供的一个命令来实现回滚。
这个命令就是 watch,该命令可以为 Redis 事务提供 check-and-set (CAS)行为。
我们可以使用 watch 命令来监视一个或多个 key,如果被监视的 key 在事务执行前被修改过那么本次事务将会被取消,也就是所谓的回滚。
只有确保被监视的 key,在事务开始前到执行 这段时间内未被修改过事务才会执行成功(类似乐观锁)
如果一次事务中存在被监视的 key,无论此次事务执行成功与否,该 key 的监视都将会在执行后失效 也就是说监视是一次性的。
总结
总的来说:Redis 事务就是一次性、顺序性、排他性的执行一个队列中的一系列命令
。没有用过或了解过 Redis 事务的网友,千万别拿它和数据库事务相比较,否则面试中肯定会吃亏!
如果你想让几个 Redis 的命令保证原子性,那我建议你使用 Lua 脚本,而不是 Redis 事务!
参考:https://mp.weixin.qq.com/s/Wqa6lpyiwaldMbt1neScQA