有道无术,术尚可求,有术无道,止于术。
本系列Redis 版本 7.2.5
源码地址:https://gitee.com/pearl-organization/study-redis-demo
文章目录
- 1. 概述
- 2. 命令
- 2.1 MULTI
- 2.2 EXEC
- 2.3 DISCARD
- 2.4 WATCH
- 2.5 UNWATCH
- 3. 事务中的错误
- 4. 和数据库事务的区别
- 4.1 原子性
- 4.2 隔离性
- 4.3 持久性
- 4.4 一致性
- 5. 脚本和事务
1. 概述
官方文档
Redis
事务:允许在单个步骤中执行一组命令,其核心命令包括 MULTI
、EXEC
、DISCARD
和 WATCH
。
Redis
事务提供了两个重要的保证:
- 所有事务中的命令都是串行化执行的,按顺序逐一执行。在事务执行过程中,不会在命令执行的中间阶段响应其他客户端的请求,这确保了命令作为单个隔离操作执行。
EXEC
命令触发事务中所有命令的执行:- 如果客户端在调用
EXEC
命令之前在事务上下文中失去了与服务器的连接,那么所有操作都不会执行。 - 一旦调用
EXEC
命令,所有操作将被执行。 - 在使用
AOF
时,确保使用单个系统调用将事务写入磁盘。如果Redi
s服务器崩溃或被系统管理员强制终止,可能会导致仅部分操作被记录。会在重新启动时检测到此条件,并报错退出。通过使用redis-check-aof
工具,可以修复追加写入文件,删除部分事务,以便服务器能够重新启动。
- 如果客户端在调用
自版本 2.2
起,Redis
在上述两个保证之外提供了额外的保证,采用了乐观锁的方式,与检查和设置(CAS
)操作非常类似。
注意:Redis
不支持事务的回滚操作,因为支持回滚会对 Redis
的简单性和性能产生影响。
2. 命令
MULTI
、 EXEC
、 DISCARD
和 WATCH
是 Redis
事务相关的命令。
2.1 MULTI
MULTI
命令用于开启一个事务,它总是返回 OK
。 MULTI
执行之后, 客户端可以继续向服务器发送任意多条命令, 这些命令不会立即被执行, 而是被放到一个队列中, 当 EXEC
命令被调用时, 所有队列中的命令才会被执行。
当客户端处于事务状态时, 所有传入的命令都会返回一个内容为 QUEUED
的状态回复, 这些被入队的命令将在 EXEC
命令被调用时执行。
示例:
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379(TX)> set k1 v1
QUEUED
127.0.0.1:6379(TX)> set k2 v2
QUEUED
2.2 EXEC
EXEC
命令用于执行事务队列内的所有命令,返回值为事务块内所有命令的返回值,按命令执行的先后顺序排列。当操作被打断时,返回空值 nil
。
示例:
127.0.0.1:6379(TX)> exec
1) OK
2) OK
2.3 DISCARD
DISCARD
命令用于取消事务,放弃执行事务队列内的所有命令,恢复连接为非事务模式。
示例:
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> set k2 v2
QUEUED
127.0.0.1:6379(TX)> discard
OK
2.4 WATCH
WATCH
命令用于监视一个或多个 key
,当被 WATCH
的 key
被修改时,事务才会被执行。如果在事务执行期间这些键被其他客户端修改,那么该事务执行会被打断。
基本语法:
WATCH key [key ...]
示例,客户端监视 k1
:
打开另外一个客户端,对 k1
进行修改操作:
之前的客户端执行事务操作时,EXEC
返回 nil
:
注意事项:
- 在
Redis 6.0.9
之前的版本中,过期的键不会导致事务中止。 WATCH
命令仅在事务中使用才有意义,因为它是为了配合事务而设计的乐观锁机制。WATCH
可以被调用多次,从调用开始,直到EXEC
调用结束。- 被监视的
key
在事务执行之前,会被Redis
服务器记录下来,并在执行EXEC
命令时检查这些键是否被修改。 - 如果在
WATCH
和EXEC
之间发生了其他客户端的写操作,事务将会失败。此时,客户端可以根据返回值nil
来判断事务执行是否成功。 - 一旦执行
EXEC
或者关闭客户端连接,所有的WATCH
都会被取消。 WATCH
命令在处理并发操作时非常有用,它可以帮助保证事务的原子性和一致性,是实现复杂事务逻辑的重要组成部分。
使用场景:
- 实现乐观锁:
WATCH
命令允许在事务执行前检查键是否被其他客户端修改,从而避免并发冲突,是实现乐观锁的重要手段之一。 - 事务中的条件控制:当事务需要根据某些条件来执行或取消执行时,可以使用
WATCH
来检查条件是否满足。
2.5 UNWATCH
UNWATCH
命令用于取消 WATCH
命令对所有 key
的监视。如果执行过 EXEC
或 DISCARD
,会自动取消监视,无需再执行 UNWATCH
。
示例:
localhost:0>UNWATCH
"OK"
3. 事务中的错误
在 Redis
事务中,可能会遇到两种类型的命令错误:
- 在调用
EXEC
之前,某些命令可能会失败无法入队。例如,命令可能存在语法错误,或者可能出现关键条件,例如内存不足等。 - 在调用
EXEC
之后,某些命令可能会失败。例如,可能对一个键执行了与其值类型不符的操作,例如对字符串值执行列表操作等。
从 Redis 2.6.5
开始,服务器会在累积命令期间检测到错误。如果发现错误,它将拒绝执行事务并在EXEC
期间返回错误,丢弃该事务。而在EXEC
之后发生的错误不会以特殊方式处理,即使事务中的某些命令失败,其他所有命令仍将被执行。
示例,在调用EXEC
之前的命令存在语法错误时,不会被添加到队列:
localhost:0>MULTI
"OK"
localhost:0>INCR a b c
"ERR wrong number of arguments for 'incr' command"
示例,命令的语法都是正确的,在调用EXEC
之后,由于 LPOP
操作的对象是一个字符串,所以这个命令执行失败,但是队列中的其他命令仍会被执行:
localhost:0>MULTI
"OK"
localhost:0>SET a abc
"QUEUED"
localhost:0>LPOP a
"QUEUED"
localhost:0>SET b efg
"QUEUED"
localhost:0>EXEC1) "OK"2) "WRONGTYPE Operation against a key holding the wrong kind of value"3) "OK"
4. 和数据库事务的区别
关系型数据库事务:事务保证一系列操作要么全部成功,要么全部失败,如果某一步出现了异常,数据就会回滚,把之前的操作撤销。数据库事务需要满足四大特性(简称ACID
),即原子性、隔离性、持久性、一致性,才能保证数据正确性。
4.1 原子性
关系型数据库事务具有严格的原子性,要么所有操作都执行成功并永久保存(提交),要么所有操作都不执行(回滚)。
Redis
事务允许一组命令作为一个原子操作进行执行。但是,不是严格的原子性,如果在执行期间发生错误,部分命令可能已经执行,而另一部分可能未执行。
4.2 隔离性
关系型数据库事务通过锁和多版本并发控制(MVCC
)等机制来实现不同事务之间的隔离性,例如读未提交、读已提交、可重复读和串行化等级别。
Redis
事务提供了一定的隔离性,但没有像传统数据库那样严格的隔离级别。不同客户端的事务可能会相互干扰,需要通过WATCH
命令显式地实现乐观锁机制。
4.3 持久性
关系型数据库事务中,事务提交后,数据会被持久化到磁盘,保证数据不会丢失。
Redis
通过持久化机制来实现持久性,但在事务提交时并没有严格保证数据已经永久保存。
4.4 一致性
关系型数据库事务中,发生错误时,数据库可以进行回滚操作,保证事务执行前后数据的一致性。
Redis
事务中的某个命令执行失败,后续的命令仍然会继续执行,不会回滚已经执行的命令,无法保证一致性。
5. 脚本和事务
从定义上来说, Redi
s 中的脚本本身就是一种事务, 所以任何在事务里可以完成的事, 在脚本里面也能完成。 并且一般来说, 使用脚本要来得更简单,并且速度更快。
因为脚本功能是 Redis 2.6
才引入的, 而事务功能则更早之前就存在了, 所以 Redis
才会同时存在两种处理事务的方法。
不过官方并不打算在短时间内就移除事务功能, 因为事务提供了一种即使不使用脚本, 也可以避免竞争条件的方法, 而且事务本身的实现并不复杂。
不过在不远的将来, 可能所有用户都会只使用脚本来实现事务也说不定。 如果真的发生这种情况的话, 那么将废弃并最终移除事务功能。