解密Redis事务(redis事务有没有原子性)

事务是什么意思

事务:是数据库操作的最小工作单元,是作为单个逻辑工作单元执行的一系列操作;这些操作作为一个整体一起向系统提交,要么都执行、要么都不执行;事务是一组不可再分割的操作集合(工作逻辑单元);

事务的四大特性:

  1. 原子性
    事务是数据库的逻辑工作单位,事务中包含的各操作要么都做,要么都不做
  2. 一致性
    事 务执行的结果必须是使数据库从一个一致性状态变到另一个一致性状态。因此当数据库只包含成功事务提交的结果时,就说数据库处于一致性状态。如果数据库系统 运行中发生故障,有些事务尚未完成就被迫中断,这些未完成事务对数据库所做的修改有一部分已写入物理数据库,这时数据库就处于一种不正确的状态,或者说是 不一致的状态。
  3. 隔离性
    一个事务的执行不能其它事务干扰。即一个事务内部的操作及使用的数据对其它并发事务是隔离的,并发执行的各个事务之间不能互相干扰。
  4. 持续性
    也称永久性,指一个事务一旦提交,它对数据库中的数据的改变就应该是永久性的。接下来的其它操作或故障不应该对其执行结果有任何影响。

Redis对事务的解释

事务是一个单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。
事务是一个原子操作:事务中的命令要么全部被执行,要么全部都不执行。
一个事务从开始到执行会经历以下三个阶段:
开始事务。
命令入队。
执行事务。

以上是redis中文文档对事务的描述,但是基本上都是废话,还有些歧义,大家姑且看看就行了,真正权威又详细的解释如下: redis官网
官网虽然写的挺详细但是是全英文的,看不懂的童鞋和我一起解密redis事务。

Redis事务

在理解redis事务之前,我们亲自试一下比较容易理解:

127.0.0.1:6379> set k1 20           //先设置一个变量
OK
127.0.0.1:6379> multi               //multi命令开启事务
OK								
127.0.0.1:6379> set k2 100          //设置变量k2
QUEUED                              //命令不会立刻执行,而是进行入栈操作
127.0.0.1:6379> incr k1             //事务中可以操作redis的任意变量
QUEUED                              //命令入栈
127.0.0.1:6379> decrby k2 20    
QUEUED                              //命令入栈
127.0.0.1:6379> get k1
QUEUED                              //命令入栈
127.0.0.1:6379> get k2
QUEUED                              //命令入栈
127.0.0.1:6379> exec                //exec触发时,才真正开始执行事务
1) OK
2) (integer) 21
3) (integer) 80
4) "21"
5) "80"                             //exec会依次执行栈中命令,并依次返回结果

看了以上的实践,我们可以做出总结:
Redis 事务的本质是一组命令的集合。事务支持一次执行多个命令,一个事务中所有命令都会被序列化。在事务执行过程,会按照顺序串行化执行队列中的命令,其他客户端提交的命令请求不会插入到事务执行命令序列中。

总的说:redis事务就是一次性、顺序性、排他性的执行一个队列中的一系列命令。

Redis事务相关命令:

watch key1 key2 … : 监视一或多个key,如果在事务执行之前,被监视的key被其他命令改动,则事务被打断 ( 类似乐观锁 )

multi : 标记一个事务块的开始( queued )

exec : 执行所有事务块的命令 ( 一旦执行exec后,之前加的监控锁都会被取消掉 )

discard : 取消事务,放弃事务块中的所有命令

unwatch : 取消watch对所有key的监控

Redis事务没有隔离级别的概念:

批量操作在发送 EXEC 命令前被放入队列缓存,并不会被实际执行,也就不存在事务内的查询要看到事务里的更新,事务外查询不能看到。所以,redis的一个事务不可能被其他事务所干扰,你可以理解成redis事务没有隔离级别的概念,也可以说redis事务具有隔离性。

Redis不保证原子性:

Redis中,单条命令是原子性执行的,但事务不保证原子性,且没有回滚。事务操作可能会遇到以下两种错误:

  • 事务在执行 EXEC 之前,入队的命令可能会出错。比如说,命令可能会产生语法错误(参数数量错误,参数名错误,等等),或者其他更严重的错误,比如内存不足(如果服务器使用 maxmemory 设置了最大内存限制的话)。
  • 命令可能在 EXEC 调用之后失败。举个例子,事务中的命令可能处理了错误类型的键,比如将列表命令用在了字符串键上面,诸如此类。
    我们分别实操测试:
  1. 语句格式语法错误,命令入队时就出错,事务内所有命令都不执行。
127.0.0.1:6379> set k1 20 
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set k2 100
QUEUED
127.0.0.1:6379> incr k1
QUEUED
127.0.0.1:6379> decr k2 20        //错误的命令
(error) ERR wrong number of arguments for 'decr' command
127.0.0.1:6379> get k1
QUEUED
127.0.0.1:6379> get k2
QUEUED
127.0.0.1:6379> exec              //全部不会执行
(error) EXECABORT Transaction discarded because of previous errors.
  1. 命令入队时成功,执行时失败,除了失败的命令,其他命令照常执行。
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set k3 string
QUEUED
127.0.0.1:6379> decr k3
QUEUED
127.0.0.1:6379> get k3
QUEUED
127.0.0.1:6379> exec
1) OK
2) (error) ERR value is not an integer or out of range
3) "string"

根据第二种情况,我们完全可以确定redis事务是不保证原子性的。

redis事务与传统数据库事务类似,但是有两点区别:

  • redis事务不保证原子性
  • redis事务不支持回滚,对于这一点,官网解释如下:

在事务运行期间,虽然Redis命令可能会执行失败,但是Redis仍然会执行事务中余下的其他命令,而不会执行回滚操作,你可能会觉得这种行为很奇怪。然而,这种行为也有其合理之处:
只有当被调用的Redis命令有语法错误时,这条命令才会执行失败(在将这个命令放入事务队列期间,Redis能够发现此类问题),或者对某个键执行不符合其数据类型的操作:实际上,这就意味着只有程序错误才会导致Redis命令执行失败,这种错误很有可能在程序开发期间发现,一般很少在生产环境发现。
Redis已经在系统内部进行功能简化,这样可以确保更快的运行速度,因为Redis不需要事务回滚的能力。
对于Redis事务的这种行为,有一个普遍的反对观点,那就是程序有可能会有缺陷(bug)。但是,你应当注意到:事务回滚并不能解决任何程序错误。例如,如果某个查询会将一个键的值递增2,而不是1,或者递增错误的键,那么事务回滚机制是没有办法解决这些程序问题的。请注意,没有人能解决程序员自己的错误,这种错误可能会导致Redis命令执行失败。正因为这些程序错误不大可能会进入生产环境,所以我们在开发Redis时选用更加简单和快速的方法,没有实现错误回滚的功能。

猜你喜欢

转载自blog.csdn.net/weixin_41674401/article/details/109600471