Redis 事务

简介

https://redis.io/docs/manual/transactions/ (opens in a new tab)

可以一次执行多个命令,本质是一组命令的集合。一个事务中的所有命令都会序列化,按顺序地串行化执行而不会被其它命令插入,不许加塞。主要是在一个队列中,一次性、顺序性、排他性的执行一系列命令。

Redis 事务 VS 数据库事务

特点详解
单独的隔离操作Redis 的事务仅仅是保证事务里的操作会被连续独占的执行,redis 命令执行是单线程架构,在执行完事务内所有指令前是不可能再去同时执行其他客户端的请求的。
没有隔离级别的概念因为事务提交前任何指令都不会被实际执行,也就不存在”事务内的查询要看到事务里的更新,在事务外查询不能看到”这种问题了。
不保证原子性Redis 的事务不保证原子性,也就是不保证所有指令同时成功或同时失败,只有决定是否开始执行全部指令的能力,没有执行到一半进行回滚的能力。
排他性Redis 会保证一个事务内的命令依次执行,而不会被其它命令插入。

示例

Redis 事务命令

  • DISCARD

    取消事务,放弃执行事务块内的所有命令

  • EXEC

    执行所有事务块内的命令

  • MULTI

    标记一个事务块的开始

  • UNWATCH

    取消 WATCH 命令对所有 KEY 的监视

  • WATCH key [key ...]

    监视一个或者多个 key,如果在事务执行之前这个(这些)key 被其他命令所改动,那么事务将被打断

正常执行

MULTI EXEC

127.0.0.1:6379> MULTI
OK
127.0.0.1:6379(TX)> SET k1 v1
QUEUED
127.0.0.1:6379(TX)> LPUSH list 1 2 3
QUEUED
127.0.0.1:6379(TX)> SET k2 v2
QUEUED
127.0.0.1:6379(TX)> INCR count
QUEUED
127.0.0.1:6379(TX)> EXEC
1) OK
2) (integer) 3
3) OK
4) (integer) 1
127.0.0.1:6379> KEYS *
1) "k2"
2) "list"
3) "k1"
4) "count"
127.0.0.1:6379> 

放弃事务

MULTI DISCARD

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
127.0.0.1:6379(TX)> INCR count
QUEUED
127.0.0.1:6379(TX)> DISCARD
OK
127.0.0.1:6379> KEYS *
(empty array)
127.0.0.1:6379>

全体连坐

Errors inside a transaction

During a transaction it is possible to encounter two kind of command errors:

  • A command may fail to be queued, so there may be an error before EXEC (opens in a new tab) is called. For instance the command may be syntactically wrong (wrong number of arguments, wrong command name, ...), or there may be some critical condition like an out of memory condition (if the server is configured to have a memory limit using the maxmemory directive).
  • A command may fail after EXEC (opens in a new tab) is called, for instance since we performed an operation against a key with the wrong value (like calling a list operation against a string value).
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
127.0.0.1:6379(TX)> SET k3 
(error) ERR wrong number of arguments for 'set' command
127.0.0.1:6379(TX)> EXEC
(error) EXECABORT Transaction discarded because of previous errors.
127.0.0.1:6379> KEYS *
(empty array)
127.0.0.1:6379>

冤头债主

Errors happening after EXEC (opens in a new tab) instead are not handled in a special way: all the other commands will be executed even if some command fails during the transaction.

It's important to note that even when a command fails, all the other commands in the queue are processed – Redis will not stop the processing of commands.

What about rollbacks?

Redis does not support rollbacks of transactions since supporting rollbacks would have a significant impact on the simplicity and performance of Redis.

Redis 不提供事务回滚的功能,开发者必须在事务执行出错后,自行恢复数据库。

注意和传统数据库事务区别,不一定要么一起成功要么一起失败。

127.0.0.1:6379> KEYS *
(empty array)
127.0.0.1:6379> SET k1 v1
OK
127.0.0.1:6379> SET email [email protected]
OK
127.0.0.1:6379> MULTI 
OK
127.0.0.1:6379(TX)> SET k1 v111
QUEUED
127.0.0.1:6379(TX)> SET k2 v2
QUEUED
127.0.0.1:6379(TX)> INCR email
QUEUED
127.0.0.1:6379(TX)> SET k3 v3
QUEUED
127.0.0.1:6379(TX)> EXEC
1) OK
2) OK
3) (error) ERR value is not an integer or out of range
4) OK
127.0.0.1:6379> GET k1
"v111"
127.0.0.1:6379> GET k3
"v3"
127.0.0.1:6379> 

watch 监控

  • Redis 使用 Watch 来提供乐观锁定,类似 CAS(Check-and-Set)

    • 乐观锁

      乐观锁(Optimistic Lock), 顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据。

      乐观锁策略:提交版本必须大记录当前版本才能执行更新。

    • 悲观锁

      悲观锁(Pessimistic Lock), 顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会 block 直到它拿到锁。

    • CAS

      Optimistic locking using check-and-set

      WATCH (opens in a new tab) is used to provide a check-and-set (CAS) behavior to Redis transactions.

      WATCH (opens in a new tab)ed keys are monitored in order to detect changes against them. If at least one watched key is modified before the EXEC (opens in a new tab) command, the whole transaction aborts, and EXEC (opens in a new tab) returns a Null reply (opens in a new tab) to notify that the transaction failed.

  • watch

    • 初始化 k1 和 balance 两个 key,先监控再开启 multi,保证两 key 变动在同一事务内。

      127.0.0.1:6379> SET balance 100
      OK
      127.0.0.1:6379> WATCH balance
      OK
      127.0.0.1:6379> MULTI
      OK
      127.0.0.1:6379(TX)> SET k1 abc
      QUEUED
      127.0.0.1:6379(TX)> SET balance 110
      QUEUED
      127.0.0.1:6379(TX)> EXEC
      1) OK
      2) OK
      127.0.0.1:6379> GET k1
      "abc"
      127.0.0.1:6379> GET balance
      "110"
      127.0.0.1:6379>
    • 有加塞篡改

      watch 命令是一种乐观锁的实现,Redis 在修改的时候会检测数据是否被更改,如果更改了,则执行失败返回 nil 值。

      127.0.0.1:6379> WATCH balance 【第1步】
      OK
      127.0.0.1:6379> MULTI 【第2步】
      OK
      127.0.0.1:6379(TX)> SET k1 v1 【第5步】
      QUEUED
      127.0.0.1:6379(TX)> SET balance 100 【第6步】
      QUEUED
      127.0.0.1:6379(TX)> EXEC 【第7步】
      (nil)
      127.0.0.1:6379> GET balance 【第8步】
      "210"
      127.0.0.1:6379> 
      127.0.0.1:6379> SET balance 210 【第3步】
      OK
      127.0.0.1:6379> GET balance 【第4步】
      "210"
      127.0.0.1:6379>
  • unwatch

    127.0.0.1:6379> WATCH balance 【第1步】
    OK
    127.0.0.1:6379> UNWATCH 【第2步】
    OK
    127.0.0.1:6379> MULTI 【第5步】
    OK
    127.0.0.1:6379(TX)> SET k1 v1 【第6步】
    QUEUED
    127.0.0.1:6379(TX)> SET balance 100 【第7步】
    QUEUED
    127.0.0.1:6379(TX)> EXEC 【第8步】
    (nil)
    127.0.0.1:6379> GET balance 【第9步】
    "210"
    127.0.0.1:6379> 
    127.0.0.1:6379> SET balance 210 【第3步】
    OK
    127.0.0.1:6379> GET balance 【第4步】
    "210"
    127.0.0.1:6379>
  • 小结

    • 一旦执行了 exec 之前之前加的监控锁都会被取消掉;
    • 当客户端连接丢失的时候(比如退出连接),所有东西都会被取消监视。

总结

  • 开启:以 MULTI 开始一个事务;
  • 入队:将多个命令入队到一个事务中,接到这些命令并不会立即执行,而是放到等在执行的事务队列里面;
  • 执行:由 EXEC 命令触发事务。