在使用关系型数据库的时候,为了保证数据的ACID(Atomicity:原子性,Consistency:一致性,Isolation:隔离性,Durability:持久性)我们经常会使用事务,要么全部提交成功,要么失败全部失败,不会存在中间状态。那么我们Redis也会有事务,只不过它不能保证原子性,Redis已经在系统内部进行功能简化,这样可以确保更快的运行速度,因为Redis不需要事务回滚的能力。
redis为我们的事务提供了四个指令:multi(开启事务),discard(丢弃),exec(执行),watch(监视)
基本使用
> multi #开启事务
OK
> set a a
QUEUED
> get a
QUEUED
> exec #执行事务
1) OK
2) "a"
> get a
"a"
执行原理如下
-
当执行multi时,事务开启
-
事务里的执行指令都存到一个队列里面,注意这里还是没有执行,可以相当于一个延迟执行
-
直到遇见exec指令,我们从队列里面获取指令,然后全部执行
关于事务操作失败的问题
当我们执行事务的时候,队列中有一个执行失败,没执行完的命令会执行吗?答案是会执行的,看下面的结果
> multi
OK
> set name mango
QUEUED
> incr name
QUEUED
> set name zhangsan
QUEUED
> exec
1) OK
2) (error) ERR value is not an integer or out of range
3) OK
> get name
"zhangsan"
丢弃(discard)
> multi
OK
> set a a
QUEUED
> get a
QUEUED
> discard
OK
> exec
(error) ERR EXEC without MULTI
> get a
(nil)
discard命令用于取消一个事务,它清空客户端的整个事务队列,然后将客户端从事务状态调整回非事务状态,最后返回字符串OK给客户端,说明事务已被取消。
> multi
OK
> multi
(error) ERR MULTI calls can not be nested
Redis 的事务是不可嵌套的,当客户端已经处于事务状态,而客户端又再向服务器发送multi时,服务器只是简单地向客户端发送一个错误,然后继续等待其他命令的入队。multi命令的发送不会造成整个事务失败,也不会修改事务队列中已有的数据。
监视(watch)
watch命令用于在事务开始之前监视任意数量的键,当调用EXEC命令执行事务时,如果任意一个被监视的键已经被其他客户端修改了,那么整个事务不再执行,直接返回失败。
那么我们来一个失败的情况,具体操作请看图片,我们就不贴代码了
在每个代表数据库的redis.h/redisDb结构类型中,都保存了一个watched_keys字典,字典的键是这个数据库被监视的键,而字典的值则是一个链表,链表中保存了所有监视这个键的客户端。
watch只能在客户端进入事务状态之前执行,在事务状态下发送watch命令会引发一个错误,但它不会造成整个事务失败,也不会修改事务队列中已有的数据。
如果说之前说的分布式锁是悲观锁,那么watch一定是乐观锁,当watch监视的key发生了变化时,我们的事务就不会执行任何操作,当key没有发生任何变化时,我们就执行这个事务。
什么情况下使用事务?
当我们需要一次性完成很多个指令或者我们需要在某次操作中不受其他指令影响,我们可以考虑使用事务,redis事务正如作者所说,我们在使用redis的时候出现异常,那么很多情况是程序设计出现了bug,不要过分依赖redis来做逻辑处理,redis本质是为了提高性能,回滚只会带来更多的问题严重影响实用性和性能。
但是如果需要满足ACID强事务类型,那么我们可以考虑使用lua脚本,但是我们同时需要考虑性能、使用成本等一些方面
一名正在抢救的coder
笔名:mangolove
CSDN地址:https://blog.csdn.net/mango_love
GitHub地址:https://github.com/mangoloveYu