Redis 事务
概述
Redis 事务允许我们一步执行多条命令。事务特性以 MULTI, EXEC, DISCARD 和 WATCH 这一组命令接口提供。事务提供了以下两点保证:
- 事务中的命令是按顺序依次一条一条执行的,但在执行过程中不会插入其他客户端在此期间发送过来的命令。这保证了事务执行和其他命令的隔离;
- Multi 开启事务,Exec 实际触发事务的执行,Exec 之前发送的命令不会立即执行,而是被服务端缓存。如果持久化方式是 AOF,Redis 保证使用单个
write(2)
系统调用将事务写到磁盘,但是如果 Redis 突然崩溃或者管理强行停止 Redis,可能会导致写到文件中事务不完整,在重启的时候,Redis 如果检测到不完整事务会拒绝启动,可以使用redis-check-aof
移除不完整事务再行启动。
Redis 2.2 之后,除了以上两点,提供了非常类似于 CAS 操作的乐观锁的方式。
事务的使用
使用 multi 开启事务,multi 的应答总是 OK。事务开启之后,用户可以发送多条命令,Redis 会缓存这些命令,提交事务使用 exec,当然也可以使用 discard 放弃事务,放弃事务会清除该事物的命令缓存。
> MULTI
OK
> INCR foo
QUEUED
> INCR bar
QUEUED
> EXEC
1) (integer) 1
2) (integer) 1
从上面的输出可以看出,事务的执行结果是一个数组,每个元素依次对应事务中每条命令的执行结果。在事务过程中发送到服务端的命令的应答都是 QUEUED,直到 exec 命令时才真正执行命令。
事务过程中的错误
一个事务过程中可能会发生两种错误:
- 命令本身有语法错误(例如:命令名称错误,命令参数错误等)。这种类型的错误命令在缓存的时候就会报错,之后执行 exec 也会报错并放弃事务;
- 命令有语义错误,例如对一个字符串类型的 key 执行 lpop 操作。
执行 exec 命令之后,如果某一条或多条命令报错,事务不会停止执行,队列中的其他的命令会照常执行。这里大家需要特别注意,Redis 的事务只能保证执行的隔离,而无法像传统数据库的事务那样保证事务的原子性。
回滚
传统数据的事务在遇到错误的时候,会回滚整个事务。但是 Redis 不支持回滚,Redis 不同于传统关系型数据库,它追求的是简单易用和极致性能,出于这方面的考虑,不支持事务回滚。
放弃事务
> SET foo 1
OK
> MULTI
OK
> INCR foo
QUEUED
> DISCARD
OK
> GET foo
"1"
上面的代码中,我们先设置 foo 的值为 1,然后开启事务,将 foo 的值加 1,最后我们使用 discard 放弃了事务,此时事务命令队列会被清空,也就是说命令并未执行,因此再次获取 foo 的时候,值仍然是 1。
基于 CAS 的乐观锁
现将要修改的 keys 监控起来,如果在 watch 之后 和 exec 执行之前,有其他客户端对监控的 keys 执行了修改,事务将会终止,并返回 null reply,用来表明事务失败。
WATCH mykey
val = GET mykey
val = val + 1
MULTI
SET mykey $val
EXEC
本例中,我们打算将 mykey 的值加 1,因此我们先监控 mykey, 然后获取 mykey 的值并加 1,然后开启事务设置新的值,假如在 exec 执行前,另外一个客户端修改了 mykey,那么事务就会终止,否则事务正常执行。
这和 CAS(compare-and-set)有什么关系呢?在 exec 执行前比较监控的 key 是否变化,如果没变化就执行,否则返回 null,返回 null,我们可以再次尝试,直到成功,这不就是典型的 CAS 用法吗?
watch 的解释
watch 命令实际上决定了 exec 是否执行,只有当监控的所有 key 都没有发生修改,exec 才会执行,否则事务终止。
注意:
- Redis 6.0.9 之前,key 过期删除不会导致事务终止;
- 事务过程中执行的命令不会触发 watch 条件的改变,因为这些命令只是被服务端缓存起来,等到 exec 才真正执行;
watch 可以调用多次,也可以一次性给 watch 传递多个 key。当 exec 执行之后,不论事务是否终止,所有的 key 都会被 unwatch。
使用 watch 实现 zpop
一个用于很好的展示 watch 用法的例子是实现一个 redis 不支持的 zpop 命令,该命令将 sorted set 中的分值最低的弹出。
WATCH myzset
element = ZRANGE myzset 0 0
MULTI
ZREM zset element
EXEC
Redis 事务和脚本
事务可以,脚本更可以且脚本更快更简单。
温馨提示:反馈需要登录