本笔记基于bilibili尚硅谷Redis学习视频整理而来
Redis主要使用MULTI, EXEC, DISCARD 和 WATCH 命令来实现事务功能。
事务可以一次执行多个命令,并带有两个重要的保证:
Redis使用 MULTI 命令标记事务开始,它总是返回OK。MULTI执行之后,客户端可以发送多条命令,Redis会把这些命令保存在队列当中,而不是立刻执行这些命令。所有的命令会在调用EXEC命令之后执行。保存命令的过程中(没有调用EXEC命令之前)可以通过DISCARD来放弃这部分操作。
案例:
下面是提交成功的情况:
假如在MULTI
阶段发生错误,那么整个提交也不会成功:
假如MULTI
阶段成功后,EXEC
阶段出现错误,那么成功的部分将会被提交,而失败部分(语义错误)将不会被执行:
上面的错误是由
incr m1
所触发的,因为不能对字符串进行incr操作。
从上面的使用案例中可以看到,Redis事务对于错误的回滚,所采用的方式跟以往的关系型数据库不一致。
组队中某个命令出现了报告错误,执行时整个的所有队列都会被取消。
如果执行阶段某个命令报出了错误,则只有报错的命令不会被执行,而其他的命令都会执行,不会回滚。
想想一个场景:有很多人拥有同一个的账户,他们尝试使用同一个账户,同时去参加双十一抢购。
假如此时账户只有10000块钱,而一个请求想买8000的商品、一个请求想买5000的商品、一个请求想买1000的商品。
他们的之间的请求几乎是同时进行。那所有人都能实现自己的想法吗?很显然,这在现实中是不被允许的:
于是我们要想办法限制某些人的行动,以达成符合正常事务逻辑的目的。对于程序员来说也就是给用户的操作”上锁“。
悲观锁(Pessimistic Lock), 顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它能重新拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。
乐观锁(Optimistic Lock), 顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新数据之前会判断一下在此期间该数据是否被被人更新。乐观锁适用于多读的应用类型,这样可以提高吞吐量。Redis就是利用这种check-and-set机制实现事务的。
可以使用版本号等机制实现乐观锁
在 Redis 中使用 watch 命令可以决定事务是执行还是回滚。一般而言,可以在 multi 命令之前使用 watch 命令监控某些键值对,然后使用 multi 命令开启事务,执行各类对数据结构进行操作的命令,这个时候这些命令就会进入队列。
当 Redis 使用 exec 命令执行事务的时候,它首先会去比对被 watch 命令所监控的键值对,如果没有发生变化,那么它会执行事务队列中的命令,提交事务;如果发生变化,那么它不会执行任何事务中的命令,而去事务回滚。无论事务是否回滚,Redis 都会去取消执行事务前的 watch 命令.
可以看到的是,这其实就是乐观锁
这个过程如图所示:
为了更直观的演示,首先打开两个命令行界面,同时连接上redis。随后使用watch
观察同一个变量,在分别multi
后,对其执行incrby n 20
的操作,随后观察两个界面分别返回的结果:
可以看到的是,先执行exec
的客户端返回了正常的返回追,而后执行的客户端则返回了nil(空)。
取消 WATCH 命令对所有 key 的监视。
如果在执行WATCH
命令之后,EXEC
命令或DISCARD
命令先被执行,那么就不需要再执行UNWATCH了。
http://doc.redisfans.com/transaction/exec.html
从上面介绍的几小节知识来看,Redis对于事务的支持不用于以往使用的关系型数据库,其主要有如下三个特点:
单独的隔离操作
没有隔离级别的概念
不保证原子性