事务机制之Redis能否实现ACID属性
什么是事务
所谓的事务,就是指对数据进行读写的一系列操作。事务在执行时,会提供专门的属性保证,包括原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)和持久性(Durability),也就是 ACID 属性。
事务ACID属性要求
首先来看原子性。原子性的要求很明确,就是一个事务中的多个操作必须都完成,或者都不完成。业务应用使用事务时,原子性也是最被看重的一个属性。
第二个属性是一致性。这个很容易理解,就是指数据库中的数据在事务执行前后是一致的。
第三个属性是隔离性。它要求数据库在执行一个事务时,其它操作无法存取到正在执行事务访问的数据。
最后一个属性是持久性。数据库执行事务后,数据的修改要被持久化保存下来。当数据库重启后,数据的值需要是被修改后的值。
Redis如何实现事务
首先,客户端需要一个显式的命令开启事务,Redis里使用MULTI来手动开启一个事务。
第二步,客户端把事务中需要执行的具体操作发送给服务端。Redis接受到这些命令后会将他们都存到一个队列当中,并不会立即执行。
第三步向服务端发送提交事务请求,Redis使用EXEC,让数据库执行第二步的那些操作。
Redis的事务机制能保证哪些属性?
原子性
如果事务正常执行,MULTI 和 EXEC 配合使用,就可以保证多个操作都完成。但是如果事务执行发生错误,则需要分三种情况:
1、在执行 EXEC 命令前,客户端发送的操作命令本身就有错误,比如语法错误,那么在入队前会被检测出来。提交一个错误命令之后,仍然可以继续提交,但是在执行EXEC命令后,Redis会返回错误,也不会执行队列中的命令。
2、事务操作入队时,命令和操作的数据类型不匹配,但 Redis 实例没有检查出错误。此时在提交EXEC命令后,Redis在执行这些操作时,到达这个不匹配的命令,会报错,但是前面的正确的命令已经执行了,此种情况无法保证事务的原子性。
Redis没有Mysql那种回滚机制。虽然提供了DISCARD 命令,但是只能用来主动放弃事务,把暂存的命令队列清空,没有回滚效果。
3、在执行事务的 EXEC 命令时,Redis 实例发生了故障,导致事务执行失败。这种情况下,如果Redis开启了AOF日志,只会有部分的事务操作被记录到AOF日志中,我们需要使用 redis-check-aof 工具检查 AOF 日志文件,这个工具可以把未完成的事务操作从 AOF 文件中去除。这样一来,我们使用 AOF 恢复实例后,事务操作不会再被执行,从而保证了原子性。
一致性
事务的一致性保证会受到错误命令、实例故障的影响。所以,我们按照命令出错和实例故障的发生时机,分成三种情况来看。
1、命令入队时就报错
这种情况下事务就不会执行,所以可以保证一致性。
2、命令入队时没报错,实际执行时报错
这种情况下,正确的命令会执行,但是错误的命令并不会执行,也不会改变数据库的一致性。
3、EXEC 命令执行时实例发生故障
如果没有开启AOF和RDB,那么重启后就没有数据,数据是一致的。
如果使用了RDB,因为RDB快照不会在事务执行的时候执行,所以事务操作成功的那部分数据不会被RDB记录,所以也是一致的。
如果使用了AOF,事务操作还没来得及被记录到AOF当中,实例就发生了故障,那么AOF恢复的数据就是一致的。如果有部分被记录到AOF当中,我们可以使用 redis-check-aof 清除事务中已经完成的操作,数据库恢复后也是一致的。
隔离性
事务的隔离性保证,会受到和事务一起执行的并发操作的影响。而事务执行又可以分成命令入队(EXEC 命令执行前)和命令实际执行(EXEC 命令执行后)两个阶段,所以,我们就针对这两个阶段,分成两种情况来分析:
1、并发操作在 EXEC 命令前执行,此时,隔离性的保证要使用 WATCH 机制来实现,否则隔离性无法保证;
WATCH 机制的作用是,在事务执行前,监控一个或多个键的值变化情况,当事务调用 EXEC 命令执行时,WATCH 机制会先检查监控的键是否被其它客户端修改了。如果修改了,就放弃事务执行,避免事务的隔离性被破坏。然后,客户端可以再次执行事务,此时,如果没有并发修改事务数据的操作了,事务就能正常执行,隔离性也得到了保证。这里需要使用watch命令。
这里的情况就是,客户端A开启事务,在输入命令的过程中,客户端B修改了A涉及到的数据。
2、并发操作在 EXEC 命令后执行,此时,隔离性可以保证。
这种情况是客户端A的事务已经全部处于队列,并且Redis已经开始处理队列中的命令,此时有客户端B发送命令修改了队列中涉及到的数据。因为Redis会优先处理队列中的命令,而不会去处理客户端B的命令,所以不会破坏事务的隔离性。
持久性
如果没有开启AOF和RDB,无法保证持久性。
开启了RDB,如果事务执行后,下一次RDB快照还没开始,就宕机,则无法保证持久性。
而AOF 模式的三种配置选项 no、everysec 和 always 都会存在数据丢失的情况,所以,事务的持久性属性也还是得不到保证。
Pipeline(管道)的使用
使用管道技术,可以一次性把命令全部打包发到服务端,服务端全部处理完成后返回。这么做好的好处,一是减少了来回网络 IO 次数,提高操作性能。二是一次性发送所有命令到服务端,服务端在处理过程中,是不会被别的请求打断的(Redis单线程特性,此时别的请求进不来),这本身就保证了隔离性。
参考
《Redis核心技术实战》