Redis事务的深潜之旅:像个老司机一样操作数据!
在学习关系型数据库时,有一个非常重要的概念——事务,它扮演着关键的角色。它确保了数据操作的完整性、一致性、隔离性和持久性。
那么你有没有想过,非关系型数据库 Redis 是如何处理并使用事务的呢?是否与关系型数据库一一致?又是否能保证 ACID?
为了解开这些疑问,也为了能更加熟练地掌握 Redis,成为一个老司机。今天,我们就来学习 Redis 事务相关的内容。
什么是Redis事务?
Redis 事务的本质是一组命令的集合。事务支持一次执行多个命令,一个事务中所有命令都会被序列化。在事务执行过程,会按照顺序串行化执行队列中的命令,其他客户端提交的命令请求不会插入到事务执行命令序列中。
总结说:Redis事务就是一次性、顺序性、排他性的执行一个队列中的一系列命令。
事务的基本使用
事务在其他语言中,一般分为以下三个阶段:
- 开启事务——Begin Transaction
- 执行业务代码,提交事务——Commit Transaction
- 业务处理中出现异常,回滚事务——Rollback Transaction
以 Java 中的事务执行为例:
1 | // 开启事务 |
Redis 中的事务从开始到结束也是要经历三个阶段:
- 开启事务
- 命令入列
- 执行事务/放弃事务
其中,开启事务使用 multi 命令,事务执行使用 exec 命令,放弃事务使用 discard 命令。
下面一一介绍关于事务的命令。
MULTI:聚会的开场白
multi 命令用于开启事务,实现代码如下:
1 | multi |
multi 命令可以让客户端从非事务模式状态,变为事务模式状态,如下图所示:
当客户端是非事务状态时,使用 multi 命令,客户端会返回结果 OK,如果客户端已经是事务状态,再执行 multi 命令会提示 multi 命令不能嵌套的错误,但不会终止客户端为事务的状态,如下所示:
1 | 127.0.0.1:6379> multi |
常规命令:聚会人员入场
客户端进入事务状态之后,执行的所有常规 Redis 操作命令(非触发事务执行或放弃和导致入列异常的命令)会依次入列,命令入列成功后会返回 QUEUED,如下代码所示:
1 | multi |
执行流程如下图所示:
命令会按照先进先出(FIFO)的顺序出入列,也就是说事务会按照命令的入列顺序,从前往后依次执行。
EXEC:行动的高潮
执行事务的命令是 exec,输入 exec 后会执行开启事务后的所有操作。执行事务示例代码如下:
1 | multi |
DISCARD:优雅的撤退
放弃事务的命令是 discard,有时候,事情并不像你预想的那样发展,你决定取消事务。DISCARD就是你的优雅撤退,所有的准备工作都被抛之脑后,大家都当做什么都没发生过一样。
放弃事务示例代码如下:
1 | multi |
具体的执行流程和 exec 一致。
WATCH:秘密特工的侦查
WATCH就是你在事务前的小心侦查,确保一切都在你的掌控之中。如果有人试图偷偷溜进来搞破坏,WATCH会立刻发出警报,保护你的数据不被篡改。
watch 命令用于客户端并发情况下,为事务提供一个乐观锁(CAS,Check And Set),也就是可以用 watch 命令来监控一个或多个变量,如果在事务的过程中,某个监控项被修改了,那么整个事务就会终止执行。
watch 基本语法如下:
1 | watch key [key ...] |
watch
示例代码如下:
1 | watch k |
注意:以上事务在执行期间,也就是开启事务(multi)之后,执行事务(exec)之前,模拟多客户端并发操作了变量 k 的值,这个时候再去执行事务,才会出现如上结果,exec 执行的结果为 nil。
可以看出,当执行 exec 返回的结果是 nil 时,表示 watch 监控的对象在事务执行的过程中被修改了。从 get k
的结果也可以印证,因为事务中设置的值 set k v2
并未正常执行。
执行流程如下图所示:
注意: watch 命令只能在客户端开启事务之前执行,在事务中执行 watch 命令会引发错误,但不会造成整个事务失败,如下代码所示:
1 | multi |
unwatch 命令用于清除所有之前监控的所有对象(键值对)。
unwatch 示例如下所示:
1 | set k v |
可以看出,即使在事务的执行过程中,k 值被修改了,因为调用了 unwatch 命令,整个事务依然会顺利执行。
举个例子
以下是事务在 Java 中的使用,代码如下:
1 | import redis.clients.jedis.Jedis; |
事务出现错误的处理
事务执行中的错误分为以下三类:
- 执行时才会出现的错误(简称:执行时错误);
- 入列时错误,不会终止整个事务;
- 入列时错误,会终止整个事务。
执行时出错
执行命令解释如下图所示:
从以上结果可以看出,即使事务队列中某个命令在执行期间发生了错误,事务也会继续执行,直到事务队列中所有命令执行完成。
入列错误不会导致事务结束
执行命令解释如下图所示:
可以看出,重复执行 multi 会导致入列错误,但不会终止事务,最终查询的结果是事务执行成功了。除了重复执行 multi 命令,还有在事务状态下执行 watch 也是同样的效果。
入列错误导致事务结束
执行命令解释如下图所示:
当然可以!让我们详细探讨一下Redis事务的局限性,揭开这种“完美的幻觉”,看看有哪些实际中的限制和注意事项。
Redis事务的局限:完美的幻觉?
Redis事务通过MULTI
、EXEC
、WATCH
和DISCARD
等命令实现了一定程度上的事务处理。然而,与传统关系型数据库的事务机制相比,Redis事务仍然存在一些局限性。
乐观锁的陷阱
概念: Redis通过WATCH
命令实现乐观锁。WATCH
命令用于监视一个或多个键,当事务执行期间,如果这些键发生变化(例如被其他客户端修改),则事务会失败。
局限性:
- 冲突频繁: 在高并发环境下,如果监视的键频繁被修改,事务成功的概率会降低,需要多次重试。
- 数据竞争: 乐观锁适用于数据冲突较少的场景,对于高冲突的场景可能并不合适。
缺乏回滚机制
概念: 在传统关系型数据库中,如果事务中的某个操作失败,可以回滚事务,撤销已经执行的操作。但是在Redis中,一旦执行EXEC
命令,所有命令都会被依次执行,不支持部分回滚。
局限性:
- 不可中断: 一旦事务开始执行,无法中途停止或回滚已经成功的操作。
- 操作不可逆: 如果事务中的某个操作失败,必须手动处理恢复或补救措施,不能依赖自动回滚。
更加深入的理解
为什么 Redis 不支持回滚?
如果你有使用关系式数据库的经验, 那么 “Redis 在事务失败时不进行回滚,而是继续执行余下的命令”这种做法可能会让你觉得有点奇怪。
以下是这种做法的优点:
- Redis 命令只会因为错误的语法而失败(并且这些问题不能在入队时发现),或是命令用在了错误类型的键上面:这也就是说,从实用性的角度来说,失败的命令是由编程错误造成的,而这些错误应该在开发的过程中被发现,而不应该出现在生产环境中。
- 因为不需要对回滚进行支持,所以 Redis 的内部可以保持简单且快速。
有种观点认为 Redis 处理事务的做法会产生 bug , 然而需要注意的是, 在通常情况下, 回滚并不能解决编程错误带来的问题。 举个例子, 如果你本来想通过 INCR 命令将键的值加上 1 , 却不小心加上了 2 , 又或者对错误类型的键执行了 INCR , 回滚是没有办法处理这些情况的。
如何理解 Redis 与事务的 ACID?
一般来说,事务有四个性质称为ACID,分别是原子性,一致性,隔离性和持久性。这是基础,但是很多文章对Redis 是否支持ACID有一些异议,我觉的有必要梳理下:
- 原子性 Atomicity
首先通过上文知道 运行期的错误是不会回滚的,很多文章由此说Redis事务违背原子性的;而官方文档认为是遵从原子性的。
Redis官方文档给的理解是,Redis的事务是原子性的:所有的命令,要么全部执行,要么全部不执行。而不是完全成功。
- 一致性 Consistency
Redis事务可以保证命令失败的情况下得以回滚,数据能恢复到没有执行之前的样子,是保证一致性的,除非Redis进程意外终结。
- 隔离性 Isolation
Redis事务是严格遵守隔离性的,原因是Redis是单进程单线程模式(v6.0之前),可以保证命令执行过程中不会被其他客户端命令打断。
但是,Redis不像其它结构化数据库有隔离级别这种设计。
- 持久性 Durability
Redis事务是不保证持久性的,这是因为Redis持久化策略中不管是RDB还是AOF都是异步执行的,不保证持久性是出于对性能的考虑。
关于 Redis 事务能不能保证 ACID这一问题,我在网上也是众说纷纭,但是在 一致性 和 隔离性 上,大家都保持着相同的看法,那就是能保证。
总结
以上就是关于 Redis 事务的全部内容了,内容并不多,没有在学习 MySQL 时的海量内容,也可以看出其实在 Redis 中并不会很频繁的使用到事务,算一个小知识点吧。
我是怎么得出这个结论的?因为小林coding写的八股文中都找不到和事务相关的内容,所以我断言它不重要。
前天晚上死活睡不着,熬了个通宵之后直接来了一个说走就走的旅行,岳阳还是很好玩的。不得不说,晚上确实是一个容易冲动消费的时间,纠结了两周的耳机还是买了,不过不知道为什么没有之前买东西的那种期待感了。
这里就不得不吐槽一下京东了,太呆瓜了,我在晚上冲动消费后,中午觉得这样不行所以决定退了,但是思考两分钟后又觉得得买,所以我就取消了我的取消申请,客服同意后,快递竟然还是被退回来。我联系客服,客服跟我说没办法了。然后我就又申请了退款,结果快递又开始配送了,不得不联系美女客服帮我退款。京东找个真人客服是真难。害的我还得再等一天。
昨天高考开始,竟然没刷到几个丢准考证和身份证的,看来今年的学生学聪明了。