我了解的Redis - 2. ACID都满足吗?

Feb. 2, 2019, 11:10 p.m.

通常聊到数据库,我们都会关心,这个数据库是否支持事务,然后事务的四大特性ACID是否都满足,分别是

  • 原子性 Atomicity
  • 一致性 Consistency
  • 隔离性 Isolation
  • 持久性 Duraility

由于 Redis 是单线程模型

  • 在并发状态下执行的事务和串行执行事务的结果是完全相同的,这天然就支持隔离性
  • 持久性,Redis 提供了两个机制,RDB和AOF,所以说也支持持久性的
  • 一致性,指的是服务从一个一致的状态到另一个一致的状态,不存在无效或错误的数据,Redis也是支持的,因为其单线程模型,以及单个命令的原子性,如果存在数据的丢失,也不影响其一致性,因为没丢的部分是一致的啊,哪怕全丢了,是个空白的数据库,也是一致的
  • 最有争议的是 Redis 的原子性,甚至有人的结论是 Redis 不支持原子性,这部分是我最想和大家分享的

原子性

Redis 的原子性是有点特殊的,可以说是区分情况的,我们先看下官方的解释(原文):

  • All the commands in a transaction are serialized and executed sequentially. 在事务中的所有命令都是连续的,而且会被顺序执行
  • Either all of the commands or none are processed, so a Redis transaction is also atomic. 所有的命令或没有命令被执行,所以 Redis 的事务是原子性的

然后在有一个段落提出了下面这个问题

  • Why Redis does not support roll backs? 为什么 Redis 不支持回滚

上面列出的官文文档里的这些信息,是矛盾的啊,或者说和我们通常的理解不同

原子性,说的不是,一个事务里的所有命令,要么全部成功,要么全部失败,如果不支持回滚的话,对于部分成功部分失败的情况,那些成功的了怎么办,回滚不了,那不就不满足原子性了吗

我想 Redis 的文档里应该不会犯这样低级的错误

在做实验之前,继续研究下文档里表述

  • Redis commands can fail only if called with a wrong syntax (and the problem is not detectable during the command queueing), or against keys holding the wrong data type: this means that in practical terms a failing command is the result of a programming errors, and a kind of error that is very likely to be detected during development, and not in production. 如果命令中使用了非法的语法,这个命令应该失败(这个情况下,错误的语法在队列中是检测不到的),相比之下,键和其数据的类型对不上,这是一个编程错误,应该在开发时检测出来,而不是在生产环境中
  • Redis is internally simplified and faster because it does not need the ability to roll back.对于 Redis 的内部是非常简单而且快速的,因为它不需要回滚

已经很清晰了,下面是实验的情况

# ====== 实验1: 老老实实地输入每一个命令, 确保合法和正确性
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> SET python yes
QUEUED
127.0.0.1:6379> SET redis yes
QUEUED
127.0.0.1:6379> EXEC
1) OK
2) OK # 结果很漂亮,2个命令都成功了


# ====== 实验2: 制造一个错误
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> SET python no no no no no # 给一个字符串对象设值, 后面的多个no值是非法的
QUEUED # 入队成功
127.0.0.1:6379> SETTTTT python go 
# 如果 SETTTTT python go 关键字SET都没输对, 直接返回错误, 入队失败
(error) ERR unknown command `SETTTTT`, with args beginning with: `python`, `go`,
127.0.0.1:6379> SET redis no
QUEUED
127.0.0.1:6379> EXEC
(error) EXECABORT Transaction discarded because of previous errors. # 2个命令都没有执行


# ====== 实验3: 制造一个文档中说的 <键和其数据的类型对不上> 的错误
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> RPUSH python go go go # python这个键是字符串对象, 用列表的命令操作它
QUEUED # 入队成功
127.0.0.1:6379> SET redis go
QUEUED
127.0.0.1:6379> EXEC
1) (error) WRONGTYPE Operation against a key holding the wrong kind of value
2) OK
# 1个成功1个失败, 是不是可以下结论, Redis根本就不是原子性的!!!

总结

  • Redis 的原子性,应该这么初步定义,要么全部执行,要么全部不执行,这儿不能用成功和失败,因为没有回滚,成功了就成功了,失败了就失败了;成功的命令不会因为失败的命令,而让自己变为失败

  • 全部不执行也不对啊,实验3不是有一个执行了吗,这里需要加一个前提条件

  • == 在所有的命令都是完全合法,提交的命令不会引起任何Redis已知的异常,Redis就可以保证事务内的所有命令按顺序执行,而且要么全部执行,要么全部不执行 ==

  • 实验中对命令不合法的情况做了验证,但没对命令全部合法时的事务执行情况验证,其结论的得出是结合了官方文档的陈述

  • 对于命令不合法的情况还区分3种情况

    • 一种是入队失败
    • 另一种是入队成功
      • 1.EXEC命令前检测出来的,那么就全部不执行
      • 2.EXEC命令之前检测出来的,完全合法的成功,不合法的失败
  • 也就是说,如果你要用 Redis 的事务的原子性,那么你要完全靠自己,严格检查所有的命令,确保是完全合法的,这样才可以最大化地符合你的预期

  • 对于单个命令来说,是否是原子的呢,答案是肯定的,因为 Redis 是单线程模型,同一时刻只会有一个命令在执行,如果这个命令出现异常就肯定是失败了

  • Redis Cluster Mode 下是不支持事务的操作的

      • MULTI EXEC 这两个命令之间
        • 1.如果只有一条命令是可以成功的
        • 2.若大于一条就会失败,无论这些命令操作的键是否都在一个Node上

写这篇文件的时候 5.0 版已经发布大半年了

运行环境

  • Redis:5.0.3
  • 个人计算机:iMac
  • 处理器:2.9GHz 四核
  • 内存:8GB
  • software:Jupyter

返回首页