Redis事务

什么是Redis事务

Redis事务的本质是一组命令的集合。事务支持一次执行多个命令,一个事务中所有命令都会被序列化。在事务执行过程,会按照顺序串行化执行队列中的命令,其他客户端提交的命令请求不会插入到事务执行命令序列中。就是在一个队列中,一次性,顺序性,排他性的执行一系列命令。

Redis的事务和MySQL的事务有什么区别

区别点 描述
单独的隔离操作 Redis的事务仅仅是保证事务里的操作会被连续独占的执行,Redis命令执行是单线程,在执行完事务内所有指令之前是不可能再去同时执行其他客户端的请求的。
没有隔离级别的概念 因为事务提交之前所有指令都不会真的被“执行“,也就不存在”事务内的查询要看到事务里的更新,在事务外查询不能看到”这种问题了。
不保证原子性 Redis事务不保证原子性,也就是不保证所有指令同时成功或者同时失败,只有决定是否开始执行全部指令的能力,没有执行到一半回滚的能力。
排他性 Redis会保证一个事务内的命令依次执行,而不会被其它命令插入。

Redis事务的使用

Redis事务的常用命令

命令 描述
DISCARD 取消事务,放弃执行事务块内的所有命令。
EXEC 执行所有事务块内的命令。
MULTI 标记一个事务块的开始。
WATCH key[key...] 监视一个(或多个)key,如果在事务执行之前这个(或这些)key被其他命令所改动,那么事务将会被打断。
UNWATCH 取消WATCH命令对所有Key的监视。

正常的执行

127.0.0.1:6379> MULTI
OK
127.0.0.1:6379(TX)> SET key1 value1
QUEUED
127.0.0.1:6379(TX)> SET key2 value2
QUEUED
127.0.0.1:6379(TX)> EXEC
1) OK
2) OK
127.0.0.1:6379> keys *
1) "key1"
2) "key2"

放弃事务

127.0.0.1:6379> MULTI
OK
127.0.0.1:6379(TX)> SET key1 value1
QUEUED
127.0.0.1:6379(TX)> SET key2 value2
QUEUED
127.0.0.1:6379(TX)> DISCARD
OK
127.0.0.1:6379> keys *
(empty array)

在事务中如果出现错误

在事务期间,可能会遇到两种类型的错误:

  • 命令无法加入队列,也就是在调用EXEC之前出现错误。比如命令有语法错误(命令名称或者参数错误),或者其他一些条件比如内存不足(如果服务器使用 maxmemory指令配置了内存限制)。

  • 命令可能在调用 EXEC之后失败,比如对具有错误值的键执行了操作(例如在字符串值上调用列表操作)。

从 Redis 2.6.5 开始,服务器将在累积命令期间检测到错误。然后,它将拒绝执行事务,在 EXEC期间返回错误,并丢弃事务。

注意对于Redis2.6.5:在Redis2.6.5之前,客户端需要通过检查排队命令的返回值来检测在执行 EXEC 之前发生的错误:如果命令回复 QUEUED,则已正确排队,否则 Redis 返回错误。如果在排队命令时出现错误,大多数客户端将中止并丢弃事务。否则,如果客户端选择继续事务,则 EXEC 命令将执行所有已成功排队的命令,而不考虑先前的错误。

语法错误

SET key3时语法错误

127.0.0.1:6379> MULTI
OK
127.0.0.1:6379(TX)> SET key1 value1
QUEUED
127.0.0.1:6379(TX)> SET key2 value2
QUEUED
127.0.0.1:6379(TX)> SET key3
(error) ERR wrong number of arguments for 'set' command
127.0.0.1:6379(TX)> EXEC
(error) EXECABORT Transaction discarded because of previous errors.

运行时错误

分别设置key1和key2为value1和value2,再开启一个事务,修改key1的值,key2自增(key2实际不能自增),再EXEC之后出现错误,此时key1设置成功,但是key2还是没变,所以事务在运行时出现错误会跳过错误继续执行。

127.0.0.1:6379> set key1 value1
OK
127.0.0.1:6379> set key2 value2
OK
127.0.0.1:6379> get key1
"value1"
127.0.0.1:6379> get key2
"value2"
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379(TX)> SET key1 newValue1
QUEUED
127.0.0.1:6379(TX)> INCR key2
QUEUED
127.0.0.1:6379(TX)> EXEC
1) OK
2) (error) ERR value is not an integer or out of range
127.0.0.1:6379> get key1
"newValue1"
127.0.0.1:6379> get key2
"value2"

WATCH监控

Redis使用WATCH来提供乐观锁,类似于CAS。乐观锁的策略:提交的版本号必须大于当前版本才能执行。

执行过程中,money被其他客户端修改

  1. 客户端1

    127.0.0.1:6379> SET key1 value1
    OK
    127.0.0.1:6379> SET money 100
    OK
    127.0.0.1:6379> WATCH money
    OK
    127.0.0.1:6379> MULTI
    OK
    127.0.0.1:6379(TX)> SET money 110
    QUEUED
    127.0.0.1:6379(TX)> SET key1 newValue1
    QUEUED
    127.0.0.1:6379(TX)> EXEC
    (nil)
    127.0.0.1:6379> get key1
    value1
    127.0.0.1:6379> get money
    "500"
    
  2. 客户端2

    127.0.0.1:6379> SET money 500
    OK
    

WATCH命令时一种乐观锁的实现,Redis在执行EXEC的时候的时候会检测数据是否被更改,如果被更改了则EXEC执行失败。上面示例中客户端1在执行EXEC之前,客户端2已经将money改为了500,所以客户端1在执行EXEC返回nil错误(整个事务都失败)。

WATCH监控是怎么实现的

Redis使用WATCH命令来决定事务是继续执行还是取消,那就需要在MULTI之前使用WATCH来监控某些键值对,然后使用MULTI命令来开启事务,执行对数据结构操作的各种命令,此时这些命令入队列。

当使用EXEC执行事务时,首先会比对WATCH所监控的键值对,如果没发生改变,它会执行事务队列中的命令,提交事务;如果发生变化,将不会执行事务中的任何命令,同时事务取消。当然无论是否取消,Redis都会取消执行事务前的WATCH命令。

EXEC触发执行事务NOyes取消监控(watch监控的所有键值对)执行事务发生变化?比较键值对取消事务WATCH命令监控键值对MULTI命令开启事务命令进入队列

为什么 Redis 不支持回滚?

  • Redis 命令只会因为错误的语法而失败(并且这些问题不能在入队时发现),或是命令用在了错误类型的键上面:这也就是说,从实用性的角度来说,失败的命令是由编程错误造成的,而这些错误应该在开发的过程中被发现,而不应该出现在生产环境中。
  • 因为不需要对回滚进行支持,所以 Redis 的内部可以保持简单且快速。