Redis事务大揭秘:一次操作,改变数据库的命运!

Redis与MySQL 事务对比

数据库中的事务是指对数据库执行一批操作,在同一个事务当中,这些操作最终要么全部执行成功,要么全部失败,不会存在部分成功的情况。

Redis 的事务和 MySQL 的事务概念上是类似的. 都是把⼀系列操作绑定成⼀组. 让这⼀组能够批量执 ⾏.

  • 弱化的原⼦性: redis 没有 "回滚机制". 只能做到这些操作 "批量执⾏". 不能做到 "⼀个失败就恢复到 初始状态"
  • 不保证⼀致性: 不涉及 "约束". 也没有回滚. MySQL 的⼀致性体现的是运⾏事务前和运⾏后 , 结果都 是合理有效的, 不会出现中间⾮法状态
  • 不需要隔离性: 也没有隔离级别, 因为不会并发执⾏事务 (redis 单线程处理请求)
  • 不需要持久性: 是保存在内存的. 是否开启持久化, 是redis-server ⾃⼰的事情, 和事务⽆关

Redis 事务本质上是在服务器上搞了⼀个 "事务队列"

每次客⼾端在事务中进⾏⼀个操作, 都会把命令先 发给服务器, 放到 "事务队列" 中(但是并不会⽴即执⾏)

⽽是会在真正收到 EXEC 命令之后, 才真正执⾏队列中的所有操作

在Redis主线程中完成的(主线程会把事务中的操作都执行完,在处理别的客户端)

比如:和女朋友出去吃烧烤,女人出门是一件麻烦的事情,我到了烧烤店,我点了牛肉串若干,羊肉串若干,五花肉若干,点完之后,我告诉服务员“人还没到,你先把单下着,但是先不着急烤”,过了一会,女朋友到了,女朋友又加了别的肉和菜,我告诉服务员:开始烤吧;此时,先点的这些肉和后点的菜和肉是一起烤的,这两组中间,是没有被插队的(不被插队,不是先抢占位置,而是先让出位置)

因此, Redis 的事务的功能相⽐于 MySQL 来说, 是弱化很多的. 只能保证事务中的这⼏个操作是 "连续 的", 不会被别的客⼾端 "加塞", 仅此⽽已.

Question:为啥redis的事务搞得这么简单,为啥不设计成MySQL一样强大呢?


Answer:MySQL的事务,在背后付出了很大的代价!空间上,要花费更多的空间来存储更多的数据;时间上,也要有更大的执行开销!正因为MySQL上述问题,才有了Redis上场的机会!

Question:什么时候使用到Redis的事务呢?


Answer:如果我们需要把多个操作打包进行,使用事务是比较合适的:秒杀,ROG掌机(持家之眼了),最大的问题:搞不到,每隔一段时间放一批货,得去jd上去和黄牛抢(不到1s就没了),超卖,放货5000台,实际如果让5001人下单成功,就是超卖了;一个典型的写法

如果不加锁,就会产生线程安全问题;可以开启事务

获取仓库中剩余的商品个数
if(个数 > 0) {
    下单成功;
    商品个数--;
}

开启事务
获取仓库中剩余的商品个数
if(个数 > 0) {
    下单成功;
    商品个数--;
}
执行事务

 Redis如果是集群部署,不支持事务操作;Redis支持lua脚本

事务操作

MULTI

开启⼀个事务. 执⾏成功返回 OK.

127.0.0.1:6379> MULTI 
OK

EXEC

真正执⾏事务.

127.0.0.1:6379> set key1 1
QUEUED
127.0.0.1:6379> set key2 2
QUEUED
127.0.0.1:6379> exec
1) OK
2) OK
  • 每次添加⼀个操作, 都会提⽰ "QUEUED", 说明命令已经进⼊客⼾端的队列了
  • 真正执⾏ EXEC 的时候, 客⼾端才会真正把上述操作发送给服务器
  • 此时就可以获取到上述 key 的值了.

DISCARD

放弃当前事务. 此时直接清空事务队列. 之前的操作都不会真正执⾏到.

127.0.0.1:6379> set key3 3
QUEUED
127.0.0.1:6379> set key4 4
QUEUED
127.0.0.1:6379> DISCARD
OK

Question:当开启事务,并且给服务器发送若干个命令之后,此时服务器重启,此时这个事务咋办?


Answer:此时的效果等同于DISCARD

127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> set key3 3
QUEUED
127.0.0.1:6379> set key4 4
QUEUED
127.0.0.1:6379> DISCARD
OK
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> set key 1
QUEUED
root@hcss-ecs-81ff:~# redis-cli
127.0.0.1:6379> EXEC
(error) ERR EXEC without MULTI

从时间来看,客户端1是先发送了set key 222,客户端2 是后发送了set key 333

由于客户端1中,得是exec执行了,才会执行set key 222,这个操作变成了实际上更晚执行的操作,最终结果就是222

WATCH

在执⾏事务的时候, 如果某个事务中修改的值, 被别的客⼾端修改了, 此时就容易出现数据不⼀致的问题,就如刚才场景中,就可以使用WATCH来监控这个key

此时,EXEC在执行上述事务命令的时候,发现key在外部有修改,此时事务就不会被执行

UNWATCH

取消对 key 的监控. 相当于 WATCH 的逆操作. 此处不做演示

WATCH的实现

  • 当开启事务的时候, 如果对 watch 的 key 进⾏修改, 就会记录当前 key 的 "版本号". (版本号是个简单 的整数, 每次修改都会使版本变⼤. 服务器来维护每个 key 的版本号情况)
  • 在真正提交事务的时候, 如果发现当前服务器上的 key 的版本号已经超过了事务开始时的版本号, 就 会让事务执⾏失败. (事务中的所有操作都不执⾏)

WATCH的实现,类似于一个”乐观锁“,乐观锁和悲观锁不是指某个具体的锁,而是指的是某一类锁的特性

  • 乐观锁:加锁之前,就有一个心理预期,预期接下来锁冲突的概率比较低
  • 悲观锁:加锁之前,就有一个心理预期,预期接下来锁冲突的概率比较高

锁冲突:两个线程针对同一个锁加锁,一个能加锁成功,另一个就得阻塞等待

锁冲突概率高和冲突概率低,接下来要做的工作是不一样的

WATCH基于版本号这样的机制来实现“乐观锁”

当执行WATCH key的时候,就会给这个key安排一个版本号,版本号可以理解为一个"整数”,每次在修改的时候,版本号都会“变大”

watch本质上是给了一个判定的条件

小结:

Redis的事务,要比MySQL的事务,简单很多

  • 原子性:Redis的食物,并不支持回滚
  • 一致性:Redis并不会保证事务执行前和执行后,内容统一
  • 持久性:Redis主要通过内存来存储数据
  • 隔离性:Redis自身作为一个单线程的服务器模型,上面处理的请求本质上都是串行执行的

命令:

  • MULTI
  • EXEC
  • DISCARD
  • WATCH/UNWATCH

Redis的lua脚本,也能起到类似于事务的效果,事务这里的任何能实现的效果,都可以使用lua脚本代替

WATCH的实现:给定一个判定条件

  • 7
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值