引言
redis的事务不像关系型数据库的事务那样完整。
“快”是redis的特征,在事务管理的过程中,使用muti命令开启事务块,当输入多条命令后,再使用exec命令执行事务块中的全部命令。
Redis事务可以保证两件事:
1、隔离性:事务是一个单独的隔离操作,这和关系型数据库的事务原则是一致的,事务中的所有命令都会顺序执行,且中途不会被其他事务打断。
2、原子性:要么全部执行,要么全不执行。
一、查看帮助
MULTI、EXEC、DISCARD、WATCH 是 Redis 事务的相关命令:
MULTI 命令用于开启一个事务,它总是返回OK,执行后,客户端可以继续向服务器发送多条命令,这些命令会被暂时存放到队列中而不执行。
EXEC 命令负责触发并执行事务中的所有命令,如果客户端在使用 MULTI 开启一个事务之后,因为掉线没有成功执行 EXEC ,那么事务中的所有命令都不会被执行。
DISCARD 客户端可以通过这个命令清空事务队列,视为放弃执行事务。
二、并发下的事务
由于Redis是单线程处理全部用户请求,因此,在并发事务中,每个命令都会排队执行,事务之间很可能出现“先删后取”的问题。
不论 multi 执行的先后,Redis 只根据最先接收到的 exec 命令来执行事务。
如上图所示,绿色事务先开启事务块,但是在执行 exec 提交事务之前,被一个删除事务提前执行,那么绿色事务就无法正确获取 key 的value。
三、WATCH 提供的 CAS
WATCH 可以让 exec 命令有条件的执行:事务只能在所有被监视的key都没有被修改的前提下执行。这是watch为redis提供的check-and-set(CAS)行为。
watch 命令可以被调用多次,对 key 的监视从 watch 执行之后生效,直到调用 exec 为止。当exec 被调用时,不管事务是否成功执行,对所有 key 的监视都会被取消。
使用 无参的 unwatch ,可以手动取消对所有 key 的监视。
如果有至少一个被监视的 key 在 exec 执行之前被修改,那么整个事务都会被取消,exec 返回 nil-reply 来表示事务已经失败。
上面的两个客户端,左侧先watch stu 监视,右侧客户端更改 stu ,左侧再开启事务并修改 stu,最后 exec ,返回结果为 nil,且stu最终并没有被改变。
这种形式的锁被称为乐观锁,它是一种非常强大的锁机制,并且因为大多数情况下,不同的客户端会访问不同的键,碰撞的情况一般都很少,所以通常并不需要进行重试。
另外,当客户端断开连接时,该客户端对 key 的监视也会被取消。
四、事务中的错误
使用事务时可能会遇到两种错误:
1、执行 exec 之前入列命令错误,如语法、参数名、参数数量等。或其他严重错误,内存不足等。
2、执行 exec 之后失败,例如,事务中的命令与处理的类型不匹配等等。
对于第一种错误,在 Redis 2.6.5 之前,客户端会检查命令入列的返回值,如果是 QUEUED,入列成功,否则入列失败。
从 2.6.5 开始,服务器会对命令入列失败的情况进行记录,并在客户端调用 exec 命令时,拒绝并自动放弃这个事务。
这种改进方式,是为了在 Redis 管道中包含事务,如果是以前的做法,那么一条拼接的多条命令如果中间某个出了问题将很难快速有效的执行,而改进后,情况就变得简单,发送事务和读取事务的回复都只需要和服务器进行一次通讯。
对于第二种错误,exec 执行时或之后出错,并没有什么特别的处理,即使事务中有某些命令在执行时产生错误,事务中的其他命令仍然会继续执行。
至于事务回复中,有一些是 OK 而有一些是 ERR,就需要由客户端自己决定如何处理,Redis 不会停止执行事务中的命令。
这就是 “Redis 在事务失败时不进行回滚,而是继续执行余下的命令”的含义。这样做有以下几点优点:
1、Redis 命令只会因为错误的语法而失败(并且这些问题不能在入队时发现),或是命令用在了错误类型的键上面:这也就是说,从实用性的角度来说,失败的命令是由编程错误造成的,而这些错误应该在开发的过程中被发现,而不应该出现在生产环境中。
2、因为不需要对回滚进行支持,所以 Redis 的内部可以保持简单且快速。
所以,通常对于精度要求不是特别严格的场合,就可以使用 redis 事务,而不应该将其用在类似银行转账的严格的事务场合。