3. Redis事务
Redis 通过 MULTI、EXEC、DISCARD、WATCH 、UNWATCH 来实现事务功能,Redis 事务具备如下几个特性
Redis 事务详解
Redis 的事务形式如下:
1 | multi |
MULTI
标志着事务的开始,可以将执行该命令的客户端从非事务态切换的事务态
在 multi 之后的命令都不会执行��全部进入事务队列中
1 | MULTI |
EXEC
按照顺序执行先前放入事务对立中的命令,然后将客户端恢复到非事务状态。
该命令返回的是一个数组,数组中的每个元素都是每个命令的返回值。该命令不会中断命令的执行,除非遇到如下几个错误:
- 服务宕机
- 入队列的命令是错误的,比如使用不存在的命令或者命令格式错误
- 使用了 WATCH,有其他客户端修改了当前事务的 key
DISCARD
清除所有先前在一个事务中放入的队列的命令,然后恢复到非事务状态
如果当前事务使用了 WATCH,那么 DISCARD 也会将当前连接监控的所有键取消监控。
1 | DISCARD |
WATCH
当某个事务需要按条件执行时,就要使用这个命令将给定的键设置为受监控的。
1 | WATCH age |
WATCH 有点儿 Java 里面 CAS 乐观锁的味道,它监视这个某个或者某些 key 在该事务期间是否被修改过,如果有至少一个 key 被修改了,那么在 EXEC 命令执行时会拒绝执行,同时向客户端返回 nil 代表当前事务执行失败。如下:
client1 | client2 |
---|---|
WATCH age | |
MULTI | |
set name chenssy1 | |
set name2 chenssy2 | set age 23 |
set age 18 | |
exec | |
运行结果:(nil) |
Redis 事务的 ACID 原则
ACID 原则我相信各位已经滚瓜烂熟了,它指数据库事务正确执行的四个基本要素的缩写。包含:原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability)(摘自百度百科)
对于 Redis 事务的 ACID 原则,先说结论:Redis 的事务满足一致性和隔离性,但是原子性和持久性不支持
原子性
整个事务中的所有操作,要么全部完成,要么全部不完成,不可能停滞在中间某个环节。事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。
注意三个关键:要么全部完成、要么全部不完成、失败回滚
Redis 单个命令的执行是具有原子性的,但是对于事务而言,由于 Redis 放弃了事务的回滚机制,在整个事务执行阶段,如果没有碰到中断的情况(上面提到的三种情况),Redis 会继续执行下去。所以 Redis 是不具备原子性的。
至于 Redis 为什么放弃回滚机制,官网也给出了答案:
- Redis 操作失败的原因只可能是语法错误或者错误的数据库类型操作,这些都是在开发层面能发现的问题不会进入到生产环境,因此不需要回滚。
- Redis 内部设计推崇简单和高性能,因此不需要回滚能力。
在事务执行过程中即使发生了错误,Redis 服务也不会放弃事务的执行,它会继续执行下去,同时也不会因为已执行的错误命令而影响后续命令的执行。
一致性
一个事务可以封装状态改变(除非它是一个只读的)。事务必须始终保持系统处于一致的状态,不管在任何给定的时间并发事务有多少。
如果数据库在执行之前是一致的,那么在事务执行之后,无论事务执行是否成功,也应该是保持一致的。
- 如果当前 Redis 采用内存模式,那么在重启之后 Redis 数据将是空白的,满足一致性
- 如果当前 Redis 的持久化选择 RDB 模式,那么在执行事务期间都不可能中断事务去执行 RDB 工作,只有在事务执行完成之后,因为某种条件触发了 RDB 才会执行 RDB 持久化工作。所以在 RDB 持久化模式下,无论事务执行是否成功,所以满足一致性。
隔离性
隔离状态执行事务,使它们好像是系统在给定时间内执行的唯一操作
通俗点将就是多个事务并发执行互不干扰。由于 Redis 使用的单线程方式来执行事务的且可以保证在整个事务执行期间不会中断去执行其他命令,所以 Redis 的事务总是可以保证隔离性的。
持久性
在事务完成以后,该事务对数据库所作的更改便持久的保存在数据库之中,并不会被回滚
- 如果 Redis 采用内存模式,因为数据不会持久化,所以持久化无法保证
- 如果 Redis 采用 RDB 模式,服务器只会在特定的条件才会触发,如果 Redis 执行事务后,没有触发 bgsave 操作,则无法保证持久化。 此时,Redis会丢失一部分数据
- 如果 Redis 采用 AOF 模式,则分为以下三种情况
- 如果选择的是 always,则会同步调用不同操作,将命令数据写入到硬盘中,所以具有持久性
- 如果选择的是 everysec 或者 no,则依然需要在特定条件下触发,否则无法命令数据可以刷入硬盘,所以不具有持久性