Redis是数据库,
自然会想到,这个数据库是否有事务这个概念?
当然有。我也是通过学习Redis才发现这个概念的,
于是开始查看相关文档,
发现,Redis的事务与常说的关系型数据库的事务有所不同,
现结合实践整理成文,帮助读者通过实践的方式理解Redis事务,
轻松应对知识考核与交流。
Redis事务支持命令拆解单步执行,通过MULTI、EXEC、DISCARD和WATCH这些命令将原始的命令拆解为单步执行的命令。Redis事务保证:
Redis的事务执行过程如下图所示,
由图可知,通过MULTI命令开启事务,
开启事务后,开始执行相关的命令,这些命令操作会记录(缓存)到队列中,
使用EXEC命令执行当前事务队列中存储的命令。
实际验证一下,测试过程如下图所示,
由图可知,MULTI开启事务后,之后的操作命令都会进入当前事务的队列,
入队后,返回QUEUED,表明正常入队,
当,完成事务准备后,通过EXEC命令,提交事务,即处理当前事务中的命令,
EXEC命令返回执行的命令个数,OK标识执行成功。
Redis取消事务通过DISCARD命令,
执行过程如下图所示,
由图可知,通过MULTI命令开启事务后,
向事务队列写入指令,
如果想取消当前事务,使用DISCARD命令。
取消事务验证过程如下图所示,
由图可知,执行DISCARD命令后,直接返回OK,表明已经取消事务,
再次查询数据时,仍是取消事务前的数据,
当前的事务没有生效。
按照关系型数据库的事务逻辑,
原子性是一荣俱荣,一损俱损,
当事务中任意一个步骤出问题,当前步骤中所有步骤均不生效,
但是,Redis不属于关系型数据库,并且,Redis秉承简洁、高效的传统,
Redis的事务并不能保证严格的原子性。
Redis的事务目前仅支持将执行任务拆分,并不支持严格的原子性。
验证Redis的非严格原子性过程如下图所示,
由图可知,使用MULTI开启事务后,所有的操作命令(非EXEC,DISCARD)只是被存储到队列,
并没有执行和校验,为什么这么讲?因为,sex是字符型kv对,使用incr命令时,只提示QUEUED,入队成功,
并没有提示错误,
当使用EXEC命令后,发现第二条命令执行异常,
第一条和第三条命令执行正常,
所以,Redis的事务不支持严格原子性,
该失败的失败,该成功的成功,互补干扰。
以查询结果为证,name值已经成功修改,sex未被成功修改,维持原值。
通过上面的测试,name的值被修改为xiaoxiao,
现在测试Redis事务的隔离性,
即客户端A开启的事务提交前,其他客户端无法读取到当前事务中进行的变更,
测试结果如下图所示,
由图可知,新开启的客户端读取到的数据为稳定的数据,
这样保证了事务修改数据期间,不会造成脏数据,但是,并发问题不能解决,下文讲。
客户端A开启事务,客户端B开启事务,
时间线如下图所示,
由图可知,客户端A和B在不同的时间执行EXEC和查询数据,
事务之间没有锁,索引,谁先执行EXEC谁先生效,
导致,并发时,事务是不安全的。
时间n和时间n+1的执行过程如下图所示,
由图可知,客户端B的先执行EXEC,因此,此时的name为客户端B生效的值:xiaxosan。
时间n+2和时间n+3执行过程如下图所示,
由图可知,客户端A执行EXEC后,获取的值为客户端A生效的值:xiaoer。
由此可知,并发时,Redis仅使用MULTI和EXEC无法保证数据安全更新。
为保证并发时事务的安全性,通过WATCH命令来给具体的键添加监控,即更新锁,
WATCH的使用规则如下图所示,
由图可知,WATCH是在开启事务前执行的,
用于约定监控哪些key,当监控的Key在当前事务执行前被其他事务修改,则不进行当前事务的变更,
执行为nil(不操作),保证并发的安全。
测试流程如下图所示,测试流程是按照下图的标号顺序进行的。
客户端A开启事务前监控name,然后,开启事务,
客户端B直接开启事务,直接修改name,提交,成功执行,
客户端A再执行EXEC时,发现,返回nil,表明键name已被其他事务修改,本事务不进行操作,
WATCH监控,保证了并发时修改键的安全性。
(1)Redis有事务概念,Redis支持将命令拆分为单步事务执行;
(2)Redis不支持原子性,无法回滚;
(3)Redis支持事务隔离性,保证不会读到未提交的脏数据;
(4)并发时,使用WATCH监控键,保证数据安全更新。