likes
comments
collection
share

Redis从入门到入坑12——事务

作者站长头像
站长
· 阅读数 10

Redis从入门到入坑12——事务

Redis 事务

概述

官网地址redis.io/docs/intera…

Redis Transactions allow the execution of a group of commands in a single step, they are centered around the commands MULTIEXECDISCARD and WATCH. Redis Transactions make two important guarantees:

  • All the commands in a transaction are serialized and executed sequentially. A request sent by another client will never be served in the middle of the execution of a Redis Transaction. This guarantees that the commands are executed as a single isolated operation.
  • The EXEC command triggers the execution of all the commands in the transaction, so if a client loses the connection to the server in the context of a transaction before calling the EXEC command none of the operations are performed, instead if the EXEC command is called, all the operations are performed. When using the append-only file Redis makes sure to use a single write(2) syscall to write the transaction on disk. However if the Redis server crashes or is killed by the system administrator in some hard way it is possible that only a partial number of operations are registered. Redis will detect this condition at restart, and will exit with an error. Using the redis-check-aof tool it is possible to fix the append only file that will remove the partial transaction so that the server can start again.

Starting with version 2.2, Redis allows for an extra guarantee to the above two, in the form of optimistic locking in a way very similar to a check-and-set (CAS) operation. This is documented later on this page


Redis 事务允许执行一组命令 在一个步骤中,它们以命令MULTI,EXECDISCARDWATCH为中心。 Redis 交易提供两个重要保证:

  • 事务中的所有命令都序列化并执行 顺序。另一个客户端发送的请求永远不会 执行 Redis 事务的过程中提供服务。 这保证了命令作为单个命令执行 隔离操作。
  • 执行命令 触发事务中所有命令的执行,因此 如果客户端在 事务在调用 EXEC 命令之前没有任何操作 ,而不是如果调用 EXEC 命令,则所有 执行操作。使用仅追加文件时,Redis 确保 使用单个 write(2) 系统调用将事务写入磁盘。 但是,如果 Redis 服务器崩溃或被系统管理员杀死 在某些困难的方式中,可能只有部分数量的操作 已注册。Redis 将在重新启动时检测到此情况,并将退出并显示错误。 使用该工具可以修复 仅附加将删除部分事务的文件,以便 服务器可以重新启动。redis-check-aof

从版本 2.2 开始,Redis 允许对 以上两个,以乐观锁定的形式以非常类似于 检查并设置 (CAS) 操作。

一句话总结:可以一次执行多个命令,本质是一组命令的集合。一个事务中的所有命令都会序列化,按顺序地串行化执行而不会被其它命令插入,不许加塞

redis事务和关系型数据库事务对比

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

使用

一个队列中,一次性、顺序性、排他性的执行一系列命令

常用命令

命令描述
MULTI标记一个事务块的开始
EXEC执行所有事务块内的命令
DISCARD取消事务,放弃执行事务块内的所有命令
WATCH监视一个或者多个KEY,如果在事务执行之前KEY被其他命令修改,则事务被打断
UNWATCH取消WATCH命令对所有KEY的监视

使用案例如下

  • 执行事务:MULTI -- EXEC
----正常执行事务
--- 使用 multi 标记事务开始
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)> lpush list ele1 ele2 ele3
QUEUED
--- 使用exec执行事务块内的所有命令
127.0.0.1:6379(TX)> exec
1) OK
2) OK
3) (integer) 3

--- 执行完毕后查看结果
127.0.0.1:6379> keys *
1) "list"
2) "k2"
3) "k1"
-----------------------------------------------------------
-----------------------------------------------------------

---异常执行事务
---1.全部失败
127.0.0.1:6379> keys *
(empty array)
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   --------------语法出错
(error) ERR wrong number of arguments for 'set' command
127.0.0.1:6379(TX)> exec  
(error) EXECABORT Transaction discarded because of previous errors.----一个语法出错,全体连坐,只要有一个命令的语法出错,redis直接返回错误所有的指令都不执行
127.0.0.1:6379> keys *  ----仍然没有数据
(empty array)

---2.正常编译通过,错误部分执行失败,不影响执行正确的结果
127.0.0.1:6379> keys *
(empty array)
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)> lpush list ele1 ele2
QUEUED
127.0.0.1:6379(TX)> incr k1
QUEUED
127.0.0.1:6379(TX)> get k2
QUEUED
127.0.0.1:6379(TX)> exec
1) OK
2) OK
3) (integer) 2
4) (error) ERR value is not an integer or out of range
5) "v2"
  • 放弃事务:MULTI -- DISCARD
--- 使用discard 取消事务
---先清空所有数据
127.0.0.1:6379> flushdb
OK
---查看,确认数据已清空
127.0.0.1:6379> keys *
(empty array)
--- 开启事务
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> rpush rlist el1 el2 el3
QUEUED
127.0.0.1:6379(TX)> lpop rlist
QUEUED
127.0.0.1:6379(TX)> set k1 v1
QUEUED
--- 放弃事务
127.0.0.1:6379(TX)> discard
OK
--- 再次查看,验证没有数据
127.0.0.1:6379> keys *
(empty array)
  • MULTI -- WATCH WATCH 命令是一种乐观锁的实现,Redis在修改的时候会检测数据是否被修改,如果数据被修改,则执行失败
--- 正常执行场景

127.0.0.1:6379> keys *
(empty array)
127.0.0.1:6379> set k1 v1
OK
127.0.0.1:6379> set watchkey 100
OK
127.0.0.1:6379> watch watchkey
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> set k1 v11
QUEUED
127.0.0.1:6379(TX)> set watchkey 200
QUEUED
127.0.0.1:6379(TX)> get k1
QUEUED
127.0.0.1:6379(TX)> get watchkey
QUEUED
127.0.0.1:6379(TX)> exec
1) OK
2) OK
3) "v11"
4) "200"
-----------------------------------------------------------
--- 中途篡改场景
第一步:
127.0.0.1:6379> keys *
(empty array)
127.0.0.1:6379> set watchkey 100
OK
127.0.0.1:6379> set k1 v1
OK
127.0.0.1:6379> watch watchkey
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> set k1 v11
QUEUED
127.0.0.1:6379(TX)> set watchkey 200
QUEUED
127.0.0.1:6379(TX)> get k1
QUEUED
127.0.0.1:6379(TX)> get watchkey
QUEUED

------------------------------------------------
第二步
----另启一个客户端连接模拟篡改 watchkey 
root@gone:/data# redis-cli -a 111111
127.0.0.1:6379> set watchkey 300
OK
------------------------------------------------
------回到之前的客户端接着执行事务
------事务执行失败
第三步:

127.0.0.1:6379(TX)> exec      ----事务执行失败
(nil)
127.0.0.1:6379> get watchkey  ----watchkey 取到的数据是被修改后的值
"300"
127.0.0.1:6379> get k1 -------此时k1的值并没有变化
"v1"
  • MULTI -- UNWATCH

执行unwatch命令必须是和watch命令在同一个客户端下执行,否则监控锁不会被取消

---正常执行(同一个客户端下执行 watch 和 unwatch)
第一步:
127.0.0.1:6379> keys *
(empty array)
127.0.0.1:6379> set k1 v1
OK
127.0.0.1:6379> set watchkey 100
OK
127.0.0.1:6379> watch watchkey
OK
-------------------------------------------------
第二步:
----另启一个客户端连接修改 watchkey 的值
root@gone:/data# redis-cli -a 111111
127.0.0.1:6379> set watchkey 300
OK
-------------------------------------------------
-----返回之前的客户端执行 unwatch ,可以发现事务正常执行
第三步:
127.0.0.1:6379> unwatch
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> set k1 v11
QUEUED
127.0.0.1:6379(TX)> set watchkey 200
QUEUED
127.0.0.1:6379(TX)> get k1
QUEUED
127.0.0.1:6379(TX)> get watchkey
QUEUED
127.0.0.1:6379(TX)> exec
1) OK
2) OK
3) "v11"
4) "200"

-------------------------------------------
---异常情况(非同一个客户端下执行 watch 和 unwatch)
第一步:
127.0.0.1:6379> keys *
(empty array)
127.0.0.1:6379> set k1 v1
OK
127.0.0.1:6379> set watchkey 100
OK
127.0.0.1:6379> watch watchkey
OK
127.0.0.1:6379> multi
OK
--------------------------------
第二步:----另启一个客户端连接并执unwatch ,然后修改 watchkey 的值
root@gone:/data# redis-cli -a 111111
127.0.0.1:6379> unwatch
OK
127.0.0.1:6379> set watchkey 300
OK

--------------------------------
第三步:回到之前的客户端继续执行事务,发现事务执行失败
127.0.0.1:6379(TX)> set watchkey 200
QUEUED
127.0.0.1:6379(TX)> set k1 v11
QUEUED
127.0.0.1:6379(TX)> exec
(nil)

总结

  • 事务以 MULTI 命令开启
  • 多个命令入队到事务中,这些命令不会立即执行,而是放到等待执行的事务队列里面
  • 事务以 EXEC 命令触发执行
  • 一旦执行了 EXEC 操作,之前加的监控都会被取消
  • 当客户端丢失连接时(比如说退出),所有的key都会被取消监视