在传统关系型数据库中,常用ACID性质来检验事务的安全性和可靠性。
在Redis中,事务总是具有原子性(Atomicity)、一致性(Consistency)、和隔离性(Isolation)的,并且当Redis运行在一些特定的持久化模式下,事务也具有耐久性(Durability)。
原子性
事务具有原子性是指,数据库事务中将多个操作看做一个整体来执行,要么执行所有的操作,要么一个操作也不执行。
事务队列
首先弄清楚 Redis 开始事务 multi 命令后,Redis 会为这个事务生成一个队列,每次操作的命令都会按照顺序插入到这个队列中。
这个队列里面的命令不会被马上执行,直到 exec 命令提交事务,所有队列里面的命令会被一次性,并且排他的进行执行。
对于Redis的事务来说,事务队列中的命令要么都执行,要么就一个也不执行,因此,Redis的事务是具有原子性的。
一下展示一个成功执行的事务,事务中所有命令都被执行:
redis> MUTLI
OK
redis> SET msg "hello"
QUEUED
redis> GET msg
QUEUED
redis> EXEC
1) OK
2) "hello"
与此相反,以下展示一个执行失败的事务,这个事务因命令入队出错而被服务器拒绝执行,事务中的所有命令都不会被执行:
redis> MUTLI
OK
redis> SET msg "hello"
QUEUED
redis> GET
(error) ERR wrong number of arguments for 'get' command
redis> EXEC
(error) EXECABORT Transaction discarded because of previous errors.
Redis的事务和一般关系型数据库事务最大的区别在于Redis不支持事务回滚机制。即使事务队列中的某个命令在执行中出现了错误,整个事务也会继续执行下去,知道事务队列中的命令执行完毕。
下面这个例子,即使RPUSH命令在执行期间出现了错误,事务的后续命令也会持续下去,并且之前的命令也不会受任何影响:
redis> SET msg "hello" # msg键是一个字符串
OK
redis> MUTLI
OK
redis> SADD fruit "apple" "banana" "cherry"
QUEUED
redis> RPUSH msg "goods bye" "bye bye" # 错误的对字符串键msg执行列表键命令
QUEUED
redis> SADD alphabet "a" "b" "c"
QUEUED
redis> EXEC
1) (integer) 3
2) (error) WRONGTYPE Operation against a key holding the wrong kind of value
3) (integer) 3
但原子性又一个特点就是要么全部成功,要么全部失败,也就是我们传统 DB 里面说的回滚。要知道 MySQL 为了能进行回滚是花了不少的代价。
Redis的作者在事务功能文档解释说,不支持事务回滚是因为这种复杂的功能和Redis最求简单高效的设计主旨不相符,并且认为,Redis执行错误通常是编程是上产生的,这种错误只会出现在开发环境中,而很少会出现在实际生产环境中,所以他认为没必要开发Redis事务回滚功能。Redis官网记录处理事务错误的方式,以及不支持回滚的原因。从严格的意义上来说 Redis 并不具备原子性。
一致性
事务具有一致性是指,如果数据库在执行事务之前是一致的,那么在执行事务之后,无论事务是否执行成功,数据库也仍一致的。
Redis通过谨慎的错误检测和简单的设计来保证了数据库的一致性。以下介绍三个Redis事务可能出错的地方,并说明Redis是如何妥善的处理这些错误,从而保证了数据的一致性。
1、入队错误
如果一个事务在入队过程中,出现了命令不存在,或者命令格式不正确等情况,Redis将拒绝这个事务:
redis> MUTLI
OK
redis> SADD fruit "apple" "banana" "cherry"
QUEUED
redis> YAHoo
(error) ERR unknown command "YAHoo"
redis> SADD alphabet "a" "b" "c"
QUEUED
redis> EXEC
(error) EXECABORT Transaction discarded because of previous errors.
服务器拒绝执行入队过程中出错的事务,多以Redis的一致性不会被影响。
2、执行错误
- 执行过程中发生的错误是一些不能再入队是被服务器发现的错误,这些错误只会在执行过程中被出发。
- 即使在事务执行过程中发现了错误的命令,服务器也不会终止事务,它会继续执行事务队列中剩余的命令,并且已执行的命令不会被错误的命令影响。
redis> SET msg "hello" # msg键是一个字符串
OK
redis> MUTLI
OK
redis> SADD fruit "apple" "banana" "cherry"
QUEUED
redis> RPUSH msg "goods bye" "bye bye" # 错误的对字符串键msg执行列表键命令
QUEUED
redis> SADD alphabet "a" "b" "c"
QUEUED
redis> EXEC
1) (integer) 3
2) (error) WRONGTYPE Operation against a key holding the wrong kind of value
3) (integer) 3
在执行事务总,错误的命令被识别出来,并且不会影响其他命令,错误的命令不会对数据做任何修改,所以Redis的一致性不会被影响。
3、宕机
无论是采用RDB或者AOF持久化方案,都可以使用RDB文件或者AOF文件进行数据恢复,从而将数据库还原至一个一致的状态。
隔离性
隔离性是指,数据库中有多个事务并发的执行,各个事务之间不会相互影响,并且在并发状态下执行的事务和串行执行的事务产生的结果是完全相同的。
Redis 因为是单线程操作,所以在隔离性上有天生的隔离机制,当 Redis 执行事务时,Redis 的服务端保证在执行事务期间不会对事务进行中断,所以,Redis 事务总是以串行的方式运行,事务也具备隔离性。
持久性
事务的持久性是指,当一个事务执行完毕,执行这个事务所得到的结果被保存在持久化的存储中,即使服务器在事务执行完成后停机了,执行的事务的结果也不会被丢失。
因为Redis事务只不过是简单的用队列包裹了一组Redis命令,Redis并没有为事务提供额外的持久化功能,所以Redis的持久性取决于Redis的持久化模式。
- 纯内存运行,不具备持久化,服务一旦停机,所有数据将丢失。
- RDB 模式,取决于 RDB 策略,只有在满足策略才会执行 BGSAVE,异步执行并不能保证 Redis 具备持久化。
- AOF 模式,只有将 appendfsync 选项设置为 always,程序才会在执行命令同步保存到磁盘,这个模式下,Redis 具备持久性。当选项值为everysec,程序每秒同步一次命令到磁盘,宕机可能发生在等待同步的那一秒内,可能会造成数据丢失,这种配置下Redis不具备持久性。当选项值为no,程序交由操作系统来觉得合适同步命令到硬盘,这种配置也可能造成数据丢失,也不具备持久性。
总结
- Redis 具备了一定的原子性,但不支持回滚。
- 如果仅仅就一致性的表述上来说,一致性就是从 A 状态经过事务到达 B 状态没有破坏各种约束性,仅就 Redis 而言不谈实现的业务,那是满意一致性。
- Redis 具备隔离性。
- 在某些持久化模式下,Redis具备持久性。
[1] 摘自 黄健宏 《Redis 设计与实现》
[2] 摘自 陈于喆 来源:51CTO技术栈