深入理解Redis、ACID特性
Redis事务简介
在Redis中,涉及到事务的基本命令有MULTI、EXEC、DISCARD、WATCH等命令。
- MULTI命令用于启动Redis的事务,将客户端置为事务状态。
- EXEC命令用于取消事务,执行从MULTI到此命令之前的命令队列,将客户端变为非事务状态。
- DISCARD命令用于取消事务,清空事务队列中的所有命令。
- WATCH用于监视键值对,在所有监视键都没有被修改的前提下,事务才能正常被执行。
目前,Redis对事务的支持还比较简单,它只能保证一个客户端请求的事务中的命令可以连续的被执行,中间过程中不会执行其他客户端发送过来的命令请求。
原子性
Redis事务的原子性是指,事务一次可以执行多个命令, 当开启事务后,多个命令依次入队,当遇到EXEC命令时,入队的命令会被看作一个整体来执行,Redis服务器对这个整体命令要么全部执行成功,要么全部执行失败。
以下是展示一个成功执行的事务。
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set msg hello
QUEUED
127.0.0.1:6379> get msg
QUEUED
127.0.0.1:6379> exec
1) OK
2) "hello"
127.0.0.1:6379>
以下是展示执行失败的事务,这个事务因命令入队出错而被服务器拒绝执行,事务中的所有命令都不会被执行。
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set data hello
QUEUED
127.0.0.1:6379> get
(error) ERR wrong number of arguments for 'get' command
127.0.0.1:6379> get data
QUEUED
127.0.0.1:6379> exec
(error) EXECABORT Transaction discarded because of previous errors.
127.0.0.1:6379> get data
(nil)
127.0.0.1:6379>
Redis的事务和传统的关系型数据库事务的最大区别在于,Redis不支持事务回滚机制,即使事务队列中的某个命令在执行期间出现错误,整个事务也会继续执行下去,直到事务队列中的所有命令都被执行完毕为止。
以下就是一个例子,首先设置键age为10,之后开启事务,并入队三条命令,其中第二条的参数是错误的,原因是incrby key的值只能为数值。当exec后,可以发现,age的值最终为19,Redis并不会因为其中某条命令出错而放弃后续命令执行。
127.0.0.1:6379> set age 10
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set name zhangsan
QUEUED
127.0.0.1:6379> incrby name 9
QUEUED
127.0.0.1:6379> incrby age 9
QUEUED
127.0.0.1:6379> exec
1) OK
2) (error) ERR value is not an integer or out of range
3) (integer) 19
127.0.0.1:6379> get age
"19"
127.0.0.1:6379>
Redis的作者在事务功能的文档中解释说,不支持事务回滚是因为这种复杂的功能和Redis追求简单高校的设计主旨不一样,况且,他认为,Redis事务的执行时错误通常都是编程错误产生,通常这种错误只会出现在开发环境,而很少在生产环境中出现,所以,他认为没必要这样做。
一致性
事务的一致性说的是,数据库在执行事务之前是一致的, 在执行事务之后,不管事务执行成功还是执行失败,数据库中的数据也应该具有一致性。 这里的一致性指的是,数据库从当前状态变为一种新的状态,数据在变化前后符合数据本身的定义和要求,同时不包含非法或无效的脏数据。
Redis事务具有一致性,它通过对命令执行的错误检测和简单的设计来保证事务执行前后数据的一致性。
下面介绍三个Redis事务可能出错的地方。
-
入队错误
如果一个事务在入队命令的时候,出现了命令不存在,或者命令的格式不正确等情况,那么Redis将拒绝执行这个事务。如下,其中test是一个不存在的命令,所以Redis服务器将拒绝执行客户端提交的事务。
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set msg hello
QUEUED
127.0.0.1:6379> test
(error) ERR unknown command `test`, with args beginning with:
127.0.0.1:6379> get msg
QUEUED
127.0.0.1:6379> exec
(error) EXECABORT Transaction discarded because of previous errors.
127.0.0.1:6379>
(根据相关文档,在Redis 2.6.5之前的版本中,即使有命令在入队过程中出错,事务一样可以执行,不过被执行的命令只包括那些正确入队的命令。)
-
执行错误
除了入队可能会发生错误以外,事务还可能在执行过程中发送错误。事务在执行过程中发生错误,服务器也不会终止事务的执行,他会继续执行事务中余下的其他命令。 -
服务器停机
如果Redis在执行事务的过程中停机,那么根据服务器所使用的持久化模式,可能有以下情况。如果服务器在无持久化内存模式下,那么重启后内存数据库将是空白,因此数据总是一致的。
如果服务器开启了RDB或AOF持久化,在执行事务的时候发生故障,不会引起数据库的不一致,重启后可以根据RDB或AOF还原事务执行之前的状态,如果开启了,但是找不到RDB或AOF文件,数据库将是空白,也能保证数据库一致性。
隔离性
事务的隔离性指的是,即使数据库中有多个事务并发地执行,各个事务之间也不会互相影响,并且在并发状态下执行的事务和串行执行的事务产生的结果完全相同。
因为Redis使用单线程的方式来执行事务,并且服务器保证,在执行事务期间不会对事务进行中断,因此,Redis 的事务总是以串行的方式运行的,并且事务也总是具有隔离性的。
持久性
事务的持久性说的是,当一个事务正确执行完成后,它对数据库的改变是永久性的,不会因为其他操作而发生改变,即使在数据库遇到故障的情况下,事务执行完成后的操作也不会丢失。
事实上,Redis数据库事务是不具有持久性的,它的事务只是简单地将一些事务命令组装到一个队列中,在进行序列化之后,按顺序执行。Redis服务器所使用的持久化方式决定了Redis事务的持久性。在Redis 的持久化方式中,不管是AOF持久化方式,还是RDB持久化方式,都是异步执行的。下面具体说明Redis的持久化方式。
如果Redis服务器没有采用任何持久化方式,那么事务不具有持久性。假如服务器发生故障(如停机、断电、崩溃),将会丢失服务器上包括事务数据在内的所有数据。
如果Redis服务器使用了AOF持久化方式:
-
当Redis配置文件(redis.conf) 中的appendfsyne属性的值为always时,可以保证Redis事务具有持久性。每当服务器执行完相关命令后,包括事务命令在内,程序都会调用执行sync同步函数,将命令数据及时保存到系统硬盘中,这就保证了事务的持久性。
-
当Redis配置文件中的appendfsync属性的值为everysec时,服务器程序会每隔1秒执行一次数据同步操作,并将数据保存到硬盘中。如果服务器发生停机故障,可能刚好发生在数据等待同步的那1秒之内,就会导致数据丢失,因此无法保证事务的持久性。
-
当Redis配置文件中的appendfsync属性的值为no时,服务器命令数据同步保存到硬盘中的操作将由操作系统来控制,因此,事务数据在同步的过程中,可能会因为一些原因而丢失,这种情况也不能保证事务的持久性。
-
当服务器在RDB持久化模式下运作时,服务器只会在特定的保存条件被满足时,才会执行BGSAVE命令,对数据库进行保存操作,并且异步执行的BGSAVE不能保证事务数据被第一时间保存到硬盘里面,因此RDB持久化模式下的事务也不具有耐久性。
但是,不论在Redis在什么模式下运作,在一个事务的最后加上SAVE命令总可以保证事务的持久性,不过这种做法的效率太低,所以并不具有使用性。
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set msg hello
QUEUED
127.0.0.1:6379> save
QUEUED
127.0.0.1:6379> exec
1) OK
2) OK
127.0.0.1:6379>