Redis面试(七):事务

2023-09-26 08:39:27 浏览数 (2)

7. 事务

7.1 Redis事务的概念

和关系型数据库事务类似,redis事务也是用来一次性地执行多条命令。

使用起来也很简单,可以用 multi 开启一个事务,然后将多个命令入队到事务的队列中,最后由exec命令触发事务,执行事务中的所有命令。

看一个简单的事务执行例子:

代码语言:javascript复制
 127.0.0.1:6379> multi
 OK
 127.0.0.1:6379> set name "hyy"
 QUEUED
 127.0.0.1:6379> set age 20
 QUEUED
 127.0.0.1:6379> incr age
 QUEUED
 127.0.0.1:6379> exec
 1) OK
 2) OK
 3) (integer) 21

可以看到,在指令和操作数的数据类型等都正常的情况下,输入EXEC后所有命令被执行成功。

Redis事务相关命令

Redis 事务功能是通过 multi、exec、discard、watch、unwatch 五个原语实现的

  • multi:开启事务,redis会将后续的命令逐个放入队列中,当 exec 命令被调用时,所有队列中的命令才会被执行。
  • exec:执行事务中的所有操作命令,返回事务块内所有命令的返回值。
  • discard:取消事务,放弃执行事务块中的所有命令。
  • watch:乐观锁,可以为 Redis 事务提供 check-and-set (CAS)行为。 监视一个或多个key,如果事务在执行前,这个key(或多个key)被其他命令修改,则事务被中断,不会执行事务中的任何命令。监控一直持续到 exec 命令。
  • unwatch:取消 watch 对所有key的监视。

7.2 Redis事务具有原子性吗?

给出结论:Redis 的事务并不是我们传统意义上理解的事务,我们都知道 单个 Redis 命令的执行是原子性的,但 Redis 没有在事务上增加任何维持原子性的机制,所以 Redis 事务的执行并不是原子性的

ACID中原子性的定义:

原子性:一个事务(transaction)中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。事务在执行过程中发生错误,会被恢复(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。

如果要验证redis事务是否满足原子性,那么需要在redis事务执行发生异常的情况下进行,下面我们分两种不同类型的错误分别测试。

  1. 语法错误或命令错误: 如果在事务中发送的命令存在语法错误或不支持的命令,Redis会返回一个错误响应,并且不会执行该命令之后的其他命令。这种情况下,整个事务会被放弃,事务中的命令不会被执行。 下面我们在事务中输入一个存在格式错误的命令,开启事务并依次输入下面的命令: 127.0.0.1:6379> multi OK 127.0.0.1:6379> set name "hyy" QUEUED 127.0.0.1:6379> incr (error) ERR wrong number of arguments for 'incr' command 127.0.0.1:6379> set age 18 QUEUED 输入的命令incr后面没有添加参数,属于命令格式不对的语法错误,这时在命令入队时就会立刻检测出错误并提示error。使用exec执行事务,查看结果输出: 127.0.0.1:6379> exec (error) EXECABORT Transaction discarded because of previous errors. 在这种情况下,只要事务中的一条命令有语法错误,在执行exec后就会直接返回错误,包括语法正确的命令在内的所有命令都不会被执行。 对此进行验证,看一下在事务中其他指令执行情况,查看set命令的执行结果,全部为空,说明指令没有被执行。 127.0.0.1:6379> get name (nil) 127.0.0.1:6379> get age (nil) 此外,如果存在命令本身拼写错误、或输入了一个不存在的命令等情况,也属于语法错误的情况,执行事务时会直接报错。
  2. 运行时错误: 在事务执行过程中,某个命令可能因为运行时错误而失败。例如,对一个不存在的键执行读取命令或尝试对字符串类型的键执行非字符串命令。在这种情况下,Redis会继续执行事务中的其他命令,并将错误信息记录在响应中。但整个事务并不会回滚,即已执行的命令不会被撤销。 这种错误在redis实际执行指令前 是无法被发现的,只能当真正执行才能够被发现,因此这样的命令是可以被事务队列接收的,不会和上面的语法错误一样立即报错。 具体看一下当事务中存在运行错误的情况,在下面的事务中,尝试对string类型数据进行incr自增操作: 127.0.0.1:6379> multi OK 127.0.0.1:6379> set name "hyy" QUEUED 127.0.0.1:6379> set age twenty QUEUED 127.0.0.1:6379> incr age QUEUED redis一直到这里都没有提示存在错误,执行exec看一下结果输出: 127.0.0.1:6379> exec 1) OK 2) OK 3) (error) ERR value is not an integer or out of range 4) (integer) 1 运行结果可以看到,虽然incr age这条命令出现了错误,但是它前后的命令都正常执行了,再看一下这些key对应的值,确实证明了其余指令都执行成功: 127.0.0.1:6379> get name hyy 127.0.0.1:6379> get age "eighteen"

对上面的事务的运行结果进行一下分析:

  • 存在语法错误/命令错误的情况下,所有命令都不会执行
  • 存在运行时错误的情况下,除执行中出现错误的命令外,其他命令都能正常执行

通过分析我们知道了redis中的事务是不满足原子性的。

7.2 Redis事务具有ACID特性吗?

  1. 原子性Atomicity:上面已经说过了,redis并不能保证原子性
  2. 一致性Consistency:一致性是在原子性 隔离性 持久性的基础上的,由于不能保证原子性,所以也不难保证一致性了
  3. 隔离性Isolation:在Redis中事务具有隔离性,因为在Redis中事务并不会并行执行,Redis是基于单线程的工作环境,所以不论如何都是串行处理事件,天然保证了隔离性。
  4. 持久性Durability:Redis事务并不会保证持久性,因为Redis只是缓存,如果Redis故障重启数据是有可能丢失的。

7.3 Redis为什么不支持回滚

在运行错误的情况下,并没有提供类似数据库中的回滚功能。那么为什么redis不支持回滚呢,官方文档给出了说明,大意如下:

  1. Redis 命令只会因为错误的语法而失败,或是命令用在了错误类型的键上面,这些问题不能在入队时发现,这也就是说,从实用性的角度来说,失败的命令是由编程错误造成的,而这些错误应该在开发的过程中被发现,而不应该出现在生产环境中.
  2. 不使用回滚,能使redis内部设计更简单,速度更快。

7.4 Redis事务的其他实现

  • 基于 Lua 脚本,Redis 可以保证脚本内的命令一次性、按顺序地执行,其同时也不提供事务运行错误的回滚,执行过程中如果部分命令运行错误,剩下的命令还是会继续运行完。
  • 基于中间标记变量,通过另外的标记变量来标识事务是否执行完成,读取数据时先读取该标记变量判断是否事务执行完成。但这样会需要额外写代码实现,比较繁琐。

我正在参与2023腾讯技术创作特训营第二期有奖征文,瓜分万元奖池和键盘手表

0 人点赞