Redis基本的事务操作

Redis事务的本质:将多个命令打包,然后一次性,按顺序的执行!

 

保证一个队列中,一次性、顺序性、排他性的执行一串命令(作用是防止别的命令插队)

一、Mysql事务四大特性

理解Redis事务之前,先来复习传统关系性数据库Mysql 中具有事务的四大特性:ACID

◈ 原子性 (Atomicity)     一个事务中的所有操作,要么全部完成,要么全部不完成!

如果事务中某条语句执行失败了,前面已经执行了的语句,会回滚到未执行前的状态,就像这个事务从来没有执行过一样。

一致性 (Consistency)   从一个正确状态切换到另一个正确状态,数据库的完整性约束未被破坏

事务执行前后都必须处于一致性状态,数据库的完整性约束包括但不限于:实体完整性(如行的主键存在且唯一)、列完整性(如字段的类型、大小、长度要符合要求)、外键约束、用户自定义完整性(如转账前后,两个账户余额的和应该不变),保证数据的正确和一致性

隔离性  (Isolation)   事务内部的操作与其他事务是隔离的,多个事务同时执行互不影响

当多个用户同时访问数据库时,比如操作同一张表,数据库为每一个用户开启的事务具有隔离性,不会被互相干扰,各搞各的互不影响!

持久性 (Durability)    事务结束后,对数据的修改是永久的!

一个事务一旦被提交了,则对数据库中的数据的改变就是永久性的,即便是在数据库发生了故障,也不会丢失提交事务的操作。

二、Redis 事务

1、Redis事务特性

◈ Redis没有隔离级别的概念!

开启事务后,所有命令会被放入队列中,实际并没有真正执行命令,只有发起exec执行命令时才会被顺序依次执行!如图:

◈ Redis单条命令是保证原子性的,但是事务不保证原子性!

同一个事务中如果有一条命令执行失败,其他命令仍然会被执行,不存在事务回滚概念,如图:

2、事务执行命令

命令描述
multi标记一个事务块的开始
exec执行所有事务块内的命令
discard取消事务,放弃执行事务块内的所有命令
watch监视多个 key ,如果在事务执行之前这些key 被其他命令所改动,那么事务将被打断
unwatch取消 WATCH 命令对所有 key 的监视

3、事务执行过程

  • 开启事务(multi)
  • 命令入队(...)
  • 执行事务(exec)

 ▏开启事务

127.0.0.1:6379> multi      # 1. multi开启事务
OK


127.0.0.1:6379(TX)> set k1 v1   # 2. 命令依次入队
QUEUED
127.0.0.1:6379(TX)> set k2 v2
QUEUED
127.0.0.1:6379(TX)> set k3 v3
QUEUED
127.0.0.1:6379(TX)> get k2
QUEUED


127.0.0.1:6379(TX)> exec    # 3. exec执行事务,并返回执行结果
1) OK
2) OK
3) OK
4) "v2"

▶ 流程如下:

1. multi 开启事务目的是将客户端的 REDIS_MULTI 选项打开,从非事务状态切换到事务状态。

preview

2. 命令入队:当客户端进入事务状态之后,继续发送命令,服务器会将这些命令全部放进一个事务队列里, 然后返回QUEUED, 表示命令已入队,如图:

注意:不是所有的命令都会被放进事务队列,以下情况命令不会放入队列,而是直接执行:

  • 客户端处于非事务状态下
  • 发送exec、discard、multi、watch这四个命令

preview

3. exec 执行事务:服务器根据客户端所保存的事务队列, 依次按照顺序执行,即采用先进先出(FIFO)方式,执行的结果也会以 FIFO 的顺序保存到一个回复队列中

当队列内的所有命令执行完毕后,exec命令会将回复队列作为执行结果返回给客户端, 客户端从事务状态返回到非事务状态, 至此, 事务执行完毕。

重复事务

127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> multi
(error) ERR MULTI calls can not be nested  # 报错:multi命令无法嵌套使用
127.0.0.1:6379(TX)>

结论:当客户端处于事务状态时,再次向服务端发送multi命令时,直接就会向客户端返回错误

取消事务

127.0.0.1:6379> multi    # 1. multi开启事务
OK

127.0.0.1:6379(TX)> set k1 v1  # 2. 命令依次入队
QUEUED
127.0.0.1:6379(TX)> set k2 v2
QUEUED
127.0.0.1:6379(TX)> set k3 v3
QUEUED

127.0.0.1:6379(TX)> discard   # 3. discard取消事务
OK

127.0.0.1:6379> mget k1 k2 k3   # 4. 查看数据是否修改
1) (nil)
2) (nil)
3) (nil)

结论:执行discard命令时, 事务会被放弃, 事务队列会被清空, 并且客户端会从事务状态中退出

事务错误

 1、编译异常

127.0.0.1:6379> multi    # 1. multi开启事务
OK

127.0.0.1:6379(TX)> set k1 v1    # 2. 命令依次入队
QUEUED
127.0.0.1:6379(TX)> set k2  # --故意不设置k2的value值,产生语法错误
(error) ERR wrong number of arguments for 'set' command  #--提示语法错误
127.0.0.1:6379(TX)> set k3 v3
QUEUED

127.0.0.1:6379(TX)> exec     # 3. exec执行事务报错:由于先前的错误导致事务取消
(error) EXECABORT Transaction discarded because of previous errors.

127.0.0.1:6379> mget k1 k2 k3  #  4. 查看数据是否操作成功
1) (nil)
2) (nil)
3) (nil)

说明:在上述中,执行 set k2  命令时,故意不设置k2的value值,让其产生语法错误,从而导致命令入队失败,服务端会立即返回错误信息给客户端,并且客户端在调用 exec 命令执行事务时,服务端会拒绝执行,并自动取消这个事务!

注意:命令入队成功会返回:QUEUED     入队失败返回错误信息提示

 2、运行异常

127.0.0.1:6379> set k1 v1  #前提:设置k1的为字符串的值 “v1"
OK

127.0.0.1:6379> multi    # 1. multi开启事务
OK

127.0.0.1:6379(TX)> set k2 v2    # 2. 命令依次入队
QUEUED
127.0.0.1:6379(TX)> incr k1 #incr命令是只支持对整数型的值执行:+1操作 
QUEUED

127.0.0.1:6379(TX)> exec   # 3.exec执行事务:命令2执行失败,k1值不是整数,无法执行+1
1) OK
2) (error) ERR value is not an integer or out of range

127.0.0.1:6379> mget k1 k2   # 4. 查看数据变化
1) "v1"
2) "v2"

说明:在上述中,对一个字符串类型的值,执行整数型的+1计算,命令没有问题,问题在于处理了错误类型的键值,从而导致运行失败了,在同一个事务中,其他命令都执行成功了,只有运行失败的命令没有成功,这就是为什么:Redis中单条命令具有原子性,但事务不具备原子性!

原子性:要么同时成功!要么同时失败!

结论:事务错误分2种

  • 编译异常:执行exec命令之前,入队命令产生语法错误,造成入队失败,或redis内存不足
  • 运行异常:调用exec之后,命令没错,而是运行导致失败了,例如处理了错误类型的键等

Redis 针对如上两种错误采用了不同的处理策略:

  •  exec 执行之前的错误,服务器会对命令入队失败的情况进行记录,客户端调用 exec 命令时,拒绝执行并自动放弃这个事务
  •  exec 执行之后所产生的错误,没有进行特别处理: 即使事务中有某个/某些命令在执行时产生了错误, 事务中的其他命令仍然会继续执行。

4、Watch 监视

Redis Watch 命令用于监视一个(或多个) key ,如果在exec事务执行之前,任意一个被监视的key被其他客户端所改动,则整个事务将被打断,不再执行, 直接返回失败。

事务只能在被监视下的键,都没有被修改的前提下执行, 否则事务就不会被执行。

什么情况下,key的监视会被取消?

  • 调用 unwatch 命令会取消对所有key的监控
  •  exec 被调用时, 不管事务是否成功执行, 对所有key的监视都会被取消
  • 当客户端断开连接时, 该客户端对键的监视也会被取消

带watch的事务

1. 假设服务端有:100块钱、2个苹果,此时客户端1 执行 “watch” 命令监视 money这个key 

2. 在客户端1 发起监视money这个key后,客户端2 客户端1 消费前,先消费了5块钱,此时服务端moeny还剩95块,

3. 紧接着,客户端1 调用exec执行事务,但是消费失败了!因为在客户端1 发起监视 "moeny" 这个key后,客户端2 修改了 "moeny" 这个key的值,所以导致客户端1 取消了事务 

如果还是同样的场景,我们没有 watch moeny ,事务不会失败,并且都能消费成功

结论

watch指令,类似乐观锁,通过 watch 命令在事务执行之前监控了多个 keys,倘若在 watch 之后有任何 key 的值发生了变化,exec 命令执行的事务都将被放弃,整个事务队列将被取消,同时返回 Null 以通知调用者事务执行失败。

5、Watch 命令的实现

在每个代表数据库的 redis.h/redisDb 结构类型中, 都保存了一个 watched_keys 字典, 字典的键是这个数据库被监视的键, 字典的值则是一个链表, 链表中保存所有监视这个键的客户端

如图:

如上图所示: 键 key1 正在被 client2 、 client5 和 client1 三个客户端监视, 其他一些键也分别被其他别的客户端监视着。

watch 命令的作用, 就是将当前客户端和要监视的键在 watched_keys 中进行关联。

如果程序想检查某个键是否被监视, 只要检查watched_keys字典中是否存在这个键; 如果程序要获取监视某个键的所有客户端, 则只要取出键的值(一个链表), 然后对链表进行遍历即可。

6、Watch 命令的实现

在任何对数据库键空间(key space)进行修改的命令成功执行之后 (比如 flushdb、set、del、lpush、sadd、zrem 诸如此类),multi.c/touchWatchedKey函数都会被调用 —— 它检查数据库的watched_keys字典, 看是否有客户端在监视已经被命令修改的键, 如果有的话, 程序将所有监视这些被修改key的客户端的REDIS_DIRTY_CAS选项打开:

当客户端发送 exec 命令、触发事务执行时, 服务器会对客户端的状态进行检查:

  • 客户端的 REDIS_DIRTY_CAS 选项已经被打开,说明被客户端监视的key至少有一个已被其他客户端修改了,事务的安全性已经被破坏。服务器会放弃执行这个事务并返回nil
  • REDIS_DIRTY_CAS 选项没有被打开,说明所有监视的key 都安全,服务器正式执行事务。

三、为什么 Redis 不支持回滚(roll back)

同一事务内,某个命令执行失败,其他执行成功的命令照常执行,不会回滚到未执行前状态

只有程序错误才会导致Redis命令执行失败(例如语法错误等),这种错误很有可能在程序开发期间发现。并且事务回滚并不能解决任何程序错误。例如,如果某个查询会将一个键的值递增2,而不是1,或者递增错误的键,那么事务回滚机制是没有办法解决这些程序问题的。

没有人能解决程序员自己的错误,这种错误可能会导致Redis命令执行失败。正因为这些程序错误不大可能会进入生产环境,所以Redis选择更加简单和快速的无回滚方式来处理事务。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值