Redis - 事务是否支持原子性
ACID 中关于原子性的定义:
原子性:一个事务(transaction)中的所有操作,或者全部完成,或者全部不完成,不会结束在中间某个环节。事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。即,事务不可分割、不可约简。
Redis事务不支持传统意义上的原子性,但可以通过Lua脚本来实现类似的功能。
事务测试
使用事务可能会遇到以下两种错误:
- 事务在执行 EXEC 之前,入队的命令可能会出错。比如,命令可能会产生语法错误(参数数量错误,参数名错误等),或者其他更严重的错误,比如内存不足(如果服务器使用 maxmemory 设置了最大内存限制的话)。
- 命令可能在 EXEC 调用之后失败。比如,事务中的命令可能处理了错误类型的键,比如将列表命令用在了字符串键上面,诸如此类。
测试:
- 第一种情况,语句格式语法错误,命令入队时就出错
127.0.0.1:6379> multi
OK
127.0.0.1:6379> hset test:hash id 10
QUEUED
127.0.0.1:6379> hset test:hash name zhangsan
QUEUED
127.0.0.1:6379> hset test:hash
(error) ERR wrong number of arguments for 'hset' command
127.0.0.1:6379> exec
(error) EXECABORT Transaction discarded because of previous errors.
127.0.0.1:6379> hgetall test:hash
(empty list or set)
127.0.0.1:6379>
可以看到这种情况在最后执行exec命令的时候提示事务错误,而且查看上面的的hash结构并没有设置成功。
- 第二种情况,命令入队时成功,执行时失败
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set a 1
QUEUED
127.0.0.1:6379> set b 2
QUEUED
127.0.0.1:6379> set c 3
QUEUED
127.0.0.1:6379> exec
1) OK
2) OK
3) OK
127.0.0.1:6379> get a
"1"
127.0.0.1:6379> get b
"2"
127.0.0.1:6379> get c
"3"
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set a 11
QUEUED
127.0.0.1:6379> lpush b 22 22 33 44
QUEUED
127.0.0.1:6379> set c 33
QUEUED
127.0.0.1:6379> exec
1) OK
2) (error) WRONGTYPE Operation against a key holding the wrong kind of value
3) OK
127.0.0.1:6379> get a
"11"
127.0.0.1:6379> get b
"2"
127.0.0.1:6379> get c
"33"
可以看到“lpush b 22 22 33 44”命令的“set 前后“a 11”、“set c 33”都执行成功了
对于 EXEC 执行之前的错误,Redis 会检查出来并返回错误自动放弃事务;但对于在 EXEC 调用后执行失败的情况,该条语句会执行失败,但事务中的其他命令仍会执行。
因此严格来说,Redis 事务确实不具备原子性的特征。
为什么Redis不支持回滚
如果有使用关系式数据库的经验, 那么 “Redis 在事务失败时不进行回滚,而是继续执行余下的命令”这种做法可能会觉得有点奇怪。
以下是这种做法的优点:
- Redis 命令只会因为错误的语法而失败(并且这些问题不能在入队时发现),或是命令用在了错误类型的键上面:这也就是说,从实用性的角度来说,失败的命令是由编程错误造成的,而这些错误应该在开发的过程中被发现,而不应该出现在生产环境中。
- 因为不需要对回滚进行支持,所以 Redis 的内部可以保持简单且快速。
鉴于没有任何机制能避免程序员自己造成的错误, 并且这类错误通常不会在生产环境中出现, 所以 Redis 选择了更简单、更快速的无回滚方式来处理事务。
Redis的设计哲学与性能考虑
- 简单与快速:Redis的设计理念是简单且快速。为了实现这一目标,Redis的内部结构被设计得相对简单。如果支持回滚机制,将增加Redis的复杂性和性能损耗,这与Redis的设计哲学不相符合。
- 避免不必要的开销:在Redis中,命令只会因为错误的语法或类型不匹配而失败,这些错误应该在开发过程中被发现,而不应该出现在生产环境中。因此,Redis选择了不支持回滚来避免不必要的开销。
总结来说,Redis 的事务在 EXEC 调用前出错会取消整个事务,但在 EXEC 调用后,即使某个命令执行失败,事务中的其他命令仍然会继续执行。