事务中可能出现的错误
执行事务机制时可能会出现以下两种错误:
• 事务在执行EXEC 之前,入队的命令可能会出错。比如说,命令可能会产生语法错误(参数数量错误,参数名错误,等等),或者其他更严重的错误,比如内存不足(如果服务器使用 maxmemory 设置了最大内存限制的话)
• 命令可能在EXEC 调用之后失败。举个例子,事务中的命令可能处理了错误类型的键,比如将列表命令用在了字符串键上面,诸如此类
对于发生在EXEC 执行之前的错误,客户端以前的做法是检查命令入队所得的返回值:如果命令入队时返回 QUEUED ,那么入队成功;否则,就是入队失败。如果有命令在入队时失败,那么大部分客户端都会停止并取消这个事务
不过,从 Redis 2.6.5 开始,服务器会对命令入队失败的情况进行记录,并在客户端调用EXEC 命令时,拒绝执行并自动放弃这个事务。
在 Redis 2.6.5 以前,Redis 只执行事务中那些入队成功的命令,而忽略那些入队失败的命令。而新的处理方式则使得在流水线(pipeline)中包含事务变得简单,因为发送事务和读取事务的回复都只需要和服务器进行一次通讯至于那些在EXEC 命令执行之后所产生的错误,并没有对它们进行特别处理:即使事务中有某个/某些命令在执行时产生了错误,事务中的其他命令仍然会继续执行
最重要的是记住这样一条,即使事务中有某条/某些命令执行失败了,事务队列中的其他命令仍然会继续执行——Redis 不会停止执行事务中的命令
为什么 Redis 不支持回滚(roll back)
如果你有使用关系式数据库的经验,那么“Redis 在事务失败时不进行回滚,而是继续执行余下的命令”这种做法可能会让你觉得有点奇怪以下是这种做法的优点:
• Redis 命令只会因为错误的语法而失败(并且这些问题不能在入队时发现),或是命令用在了错误类型的键上面:这也就是说,从实用性的角度来说,失败的命令是由编程错误造成的,而这些错误应该在开发的过程中被发现,而不应该出现在生产环境中
• 因为不需要对回滚进行支持,所以 Redis 的内部可以保持简单且快速有种观点认为 Redis 处理事务的做法会产生 bug ,然而需要注意的是,在通常情况下,回滚并不能解决编
程错误带来的问题。举个例子,如果你本来想通过INCR 命令将键的值加上 1 ,却不小心加上了 2 ,又或者对错误类型的键执行了INCR ,回滚是没有办法处理这些情况的
鉴于没有任何机制能避免程序员自己造成的错误,并且这类错误通常不会在生产环境中出现,所以 Redis 选
择了更简单、更快速的无回滚方式来处理事务
事务的 ACID 性质
在传统的关系式数据库中,常常用 ACID 性质来检验事务功能的安全性。
Redis 事务保证了其中的一致性(C)和隔离性(I),但并不保证原子性(A)和持久性(D)。
以下四小节是关于这四个性质的详细讨论
原子性(Atomicity)
单个 Redis 命令的执行是原子性的,但 Redis 没有在事务上增加任何维持原子性的机制,所以
Redis 事务的执行并不是原子性的。
如果一个事务队列中的所有命令都被成功地执行,那么称这个事务执行成功。
另一方面,如果 Redis 服务器进程在执行事务的过程中被停止——比如接到 KILL 信号、宿主
机器停机,等等,那么事务执行失败。
当事务失败时,Redis 也不会进行任何的重试或者回滚动作。
一致性(Consistency)
Redis 的一致性问题可以分为三部分来讨论:入队错误、执行错误、Redis 进程被终结。
入队错误
在命令入队的过程中,如果客户端向服务器发送了错误的命令,比如命令的参数数量
不对,等等,那么服务器将向客户端返回一个出错信息,并且将客户端的事务状态设为
REDIS_DIRTY_EXEC 。
当客户端执行 EXEC 命令时,Redis 会拒绝执行状态为 REDIS_DIRTY_EXEC 的事务,并返回失
败信息。
redis 127.0.0.1:6379> MULTI
OK
redis 127.0.0.1:6379> set key
(error) ERR wrong number of arguments for 'set' command
redis 127.0.0.1:6379> EXISTS key
QUEUED
redis 127.0.0.1:6379> EXEC
(error) EXECABORT Transaction discarded because of previous errors.
因此,带有不正确入队命令的事务不会被执行,也不会影响数据库的一致性
执行错误
如果命令在事务执行的过程中发生错误,比如说,对一个不同类型的 key 执行了错误的操作,
那么 Redis 只会将错误包含在事务的结果中,这不会引起事务中断或整个失败,不会影响已执
行事务命令的结果,也不会影响后面要执行的事务命令,所以它对事务的一致性也没有影响。
Redis 进程被终结
如果 Redis 服务器进程在执行事务的过程中被其他进程终结,或者被管理员强制杀死,那么根
据 Redis 所使用的持久化模式,可能有以下情况出现:
• 内存模式:如果 Redis 没有采取任何持久化机制,那么重启之后的数据库总是空白的,所
以数据总是一致的。
• RDB 模式:在执行事务时,Redis 不会中断事务去执行保存 RDB 的工作,只有在事务执
行之后,保存 RDB 的工作才有可能开始。所以当 RDB 模式下的 Redis 服务器进程在事
务中途被杀死时,事务内执行的命令,不管成功了多少,都不会被保存到 RDB 文件里。
恢复数据库需要使用现有的 RDB 文件,而这个 RDB 文件的数据保存的是最近一次的数
据库快照(snapshot),所以它的数据可能不是最新的,但只要 RDB 文件本身没有因为
其他问题而出错,那么还原后的数据库就是一致的。
• AOF 模式:因为保存 AOF 文件的工作在后台线程进行,所以即使是在事务执行的中途,
保存 AOF 文件的工作也可以继续进行,因此,根据事务语句是否被写入并保存到 AOF
文件,有以下两种情况发生:
1)如果事务语句未写入到 AOF 文件,或 AOF 未被 SYNC 调用保存到磁盘,那么当进
程被杀死之后,Redis 可以根据最近一次成功保存到磁盘的 AOF 文件来还原数据库,只
要 AOF 文件本身没有因为其他问题而出错,那么还原后的数据库总是一致的,但其中的
数据不一定是最新的。
2)如果事务的部分语句被写入到 AOF 文件,并且 AOF 文件被成功保存,那么不完整的
事务执行信息就会遗留在 AOF 文件里,当重启 Redis 时,程序会检测到 AOF 文件并不
完整,Redis 会退出,并报告错误。需要使用 redis-check-aof 工具将部分成功的事务命令
移除之后,才能再次启动服务器。还原之后的数据总是一致的,而且数据也是最新的(直
到事务执行之前为止)。
隔离性(Isolation)
Redis 是单进程程序,并且它保证在执行事务时,不会对事务进行中断,事务可以运行直到执
行完所有事务队列中的命令为止。因此,Redis 的事务是总是带有隔离性的。
持久性(Durability)
因为事务不过是用队列包裹起了一组 Redis 命令,并没有提供任何额外的持久性功能,所以事
务的持久性由 Redis 所使用的持久化模式决定:
• 在单纯的内存模式下,事务肯定是不持久的。
• 在 RDB 模式下,服务器可能在事务执行之后、RDB 文件更新之前的这段时间失败,所
以 RDB 模式下的 Redis 事务也是不持久的。
• 在 AOF 的“总是 SYNC ”模式下,事务的每条命令在执行成功之后,都会立即调用 fsync
或 fdatasync 将事务数据写入到 AOF 文件。但是,这种保存是由后台线程进行的,主
线程不会阻塞直到保存成功,所以从命令执行成功到数据保存到硬盘之间,还是有一段
非常小的间隔,所以这种模式下的事务也是不持久的。
其他 AOF 模式也和“总是 SYNC ”模式类似,所以它们都是不持久的