事务的本质是一组命令的集合。事务支持一次执行多个命令,一个事务中所有命令都会被序列化。在事务执行过程,会按照顺序串行化执行队列中的命令,其他客户端提交的命令请求不会插入到事务执行命令序列中。

# 相关命令

MULTI 、 EXEC 、 DISCARD 和 WATCH 是 Redis 事务相关的命令。

  • multi:开启事务
  • exec:执行事务
  • discard:取消事务
  • watch:监视若干个 key,事务执行前,key 被其他命令修改,则事务中断(乐观锁)。
  • unwatch:取消监视。

# 错误处理机制

  • 语法错误(编译器错误):开启事务后,如果是命令输入错误之类的,redis 会立即提示,此时若提交事务就会失败,并且事务中其他命令改动的数据都不变。
127.0.0.1:6379> set k1 v1
OK
127.0.0.1:6379> set k2 v2
OK
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> set k1 11
QUEUED		-- 事务状态下的 ml 返回 queued,表示命令进入事务队列
127.0.0.1:6379> sets k2 22
(error) ERR unknown command `sets`, with args beginning with: `k2`, `22`, 
127.0.0.1:6379> exec
(error) EXECABORT Transaction discarded because of previous errors.
127.0.0.1:6379> get k1
"v1"
127.0.0.1:6379> get k2
"v2"
  • 类型错误(运行时错误):在运行时检测类型错误,最终导致事务提交失败,此时事务没有回滚,而是跳过错误命令继续执行。
127.0.0.1:6379> set k1 v1
OK
127.0.0.1:6379> set k1 v2
OK
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> set k1 11
QUEUED
127.0.0.1:6379> lpush k2 22		-- 将 k2 当作 list
QUEUED
127.0.0.1:6379> EXEC
1) OK
2) (error) WRONGTYPE Operation against a key holding the wrong kind of value
127.0.0.1:6379> get k1
"11"
127.0.0.1:6379> get k2
"v2"

执行失败(运行错误)后会继续执行而不是回滚,是 redis 考虑到事务命令执行失败都是程序员自己写的代码有问题,即使回滚,也无法解决这些问题,程序员更应该编写正确的命令再使用到生产环境,同时 redis 因为没有回滚,也保持简洁。

# 乐观锁

watch 命令为事务提供了 CAS 行为,之前 J.U.C 系列文章中讲的许多锁,利用 CAS 特性不断重试直到原子修改数据。

在 WATCH 执行之后, EXEC 执行之前, 有其他客户端修改了 mykey 的值, 那么当前客户端的事务就会失败。 程序需要做的, 就是不断重试这个操作, 直到没有发生碰撞为止。大多数情况下, 不同的客户端会访问不同的键, 碰撞的情况一般都很少, 所以通常并不需要进行重试。

watch 实现监视

需要在 MULTI 之前使用 WATCH 来监控某些键值对,然后使用 MULTI 命令来开启事务,执行对数据结构操作的各种命令,此时这些命令入队列。

当使用 EXEC 执行事务时,首先会比对 WATCH 所监控的键值对,如果没发生改变,它会执行事务队列中的命令,提交事务;如果发生变化,将不会执行事务中的任何命令,同时事务回滚。当然无论是否回滚,Redis 都会取消执行事务前的 WATCH 命令

# 一些理解

  • redis 不支持回滚:redis 命令因为错误的语法(运行时错误)失败,然后继续执行后面的命令,redis 认为这些都是程序员可以自己规避的,错误的语法也不应该放在生产环境中,所以 redis 没有回滚机制,保持简单迅速。

  • 事务 ACID:ACID 就是原子性(atomicity),一致性(consistency),隔离性(isolation),持久性(durability)。

    • 原子性:运行期错误不会回滚。redis 认为事务仍然是原子性的,所有命令要么全部执行,要么全部不执行,而不是完全成功。之前说了,运行期错误应该在生产环境杜绝出现。
    • 一致性:事务命令失败得以回滚,保证一致性。
    • 隔离性:单进程单线程模式。
    • 持久性:RDB 和 AOF 都是异步执行(也可以阻塞,save 命令嘛)。

# 参考

https://pdai.tech/md/db/nosql-redis/db-redis-x-trans.html

https://www.yuque.com/qingkongxiaguang/spring/nka2vz#0f23247b