zoukankan      html  css  js  c++  java
  • Redis的事务不是原子性的

    1.事务的四大特性

      原子性(Atomicity):化学中的原子指不可再分的基本微粒,数据库中原子性强调事务是一个不可分割的整体,事务开始后所有操作要么全部成功,要么全部失败,不可能停滞在中间某个环节。如果事务执行过程中出错就会回滚到事务开始前的状态,所有的操作就像没有发生一样不会对数据库有任何影响。简单来说要么就是全部执行,即使有一条失败,也全部不执行。

      一致性(Consistency):事务必须使数据库从一个一致性状态变换到另一个一致性状态,即一个事务执行之前和执行之后都必须处于一致性状态。拿转账来说,假设用户A和用户B两者的钱加起来一共是5000,那么不管A和B之间如何转账,转几次账,事务结束后两个用户的钱相加起来应该还是5000,这就是事务的一致性。

      隔离性(Isolation):当多个用户并发访问数据库时,比如操作同一张表时,数据库为每一个用户开启的事务,不能被其他事务的操作所干扰,多个并发事务之间要相互隔离,比如A正在从一张银行卡中取钱,在A取钱的过程结束前,B不能向这张卡转入钱。

      持久性(Durability):一个事务一旦被提交,则对数据库的所有更新将被保存到数据库中,不能回滚。

     

    2.Redis的事务是什么样子?

      在此之前,学习redis开发与运维一书,书中第三章多次提到原子操作,所以搜索网上的文章搞懂这个问题。

      首先我们给出一个定义:redis的事务中,一次执行多条命令,本质是一组命令的集合,一个事务中所有的命令将被序列化,即按顺序执行而不会被其他命令插入

      在redis中,事务的作用就是在一个队列中一次性、顺序性、排他性的执行一系列的命令。

     

    • 事务的生命周期

    1.事务的创建:使用MULTI开启一个事务

    2.加入队列:在开启事务的时候,每次操作的命令将会被插入到一个队列中,同时这个命令并不会被真的执行

    3.EXEC命令进行提交事务

     

    • 常用的关于事务命令的解释

    1.MULTI:使用该命令,标记一个事务块的开始,通常在执行之后会回复OK,(但不一定真的OK),这个时候用户可以输入多个操作来代替逐条操作,redis会将这些操作放入队列中。

    2.EXEC:执行这个事务内的所有命令

    3.DISCARD:放弃事务,即该事务内的所有命令都将取消

    4.WATCH:监控一个或者多个key,如果这些key在提交事务(EXEC)之前被其他用户修改过,那么事务将执行失败,需要重新获取最新数据重头操作(类似于乐观锁)。

    5.UNWATCH:取消WATCH命令对多有key的监控,所有监控锁将会被取消。

     

    • 锁的概念

    乐观锁:就像它的名字,不会认为数据不会出错,它不会为数据上锁,但是为了保证数据的一致性,它会在每条记录的后面添加一个标记(类似于版本号),假设A 获取K1这条标记,得到了k1的版本号是1,并对其进行修改,这个时候B也获取了k1这个数据,当然,B获取的版本号也是1,同样也对k1进行修改,这个时候,如果B先提交了,那么k1的版本号将会改变成2,这个时候,如果A提交数据,他会发现自己的版本号与最新的版本号不一致,这个时候A的提交将不会成功,A的做法是重新获取最新的k1的数据,重复修改数据、提交数据。

    悲观锁:这个模式将认定数据一定会出错,所以它的做法是将整张表锁起来,这样会有很强的一致性,但是同时会有极低的并发性(常用语数据库备份工作,类似于表锁)。

     

    • 执行一次具体看看redis的事务机制

    首先开启事务,并向数据库中存储4条数据,可以看到每执行一条命令的时候都会显示入队,并不会返回执行结果,说明redis中在事务提交之前,其内部的所有命令将不会被执行:

    那么,如果中间有命令出错了会怎样呢?现在随便打几个字符试一试:

      可以看出,在第三条命令中随便打了几个字符,提交事务的时候并没有成功,这也很符合我们对事务的理解,嗯~具有原子性。但是,有一个细节,那就是错误命令在我输入的时候就已经报错了,也就是说这个条错误命令在进入队列的时候redis就已经知道这是一条错误命令,这样,整个事务的命令将全部失败,那么,有没有一种可能某个错误指令在进入队列的时候redis还没有发现他的错误呢?我们试一试下面这个例子:

      问题出现了,我们可以看到,name+1这条指令其实是错误的,但是提交事务的时候会发现,这条错误命令确实没有执行,但是其他正确的命令却执行,这是为什么的?

      原因是在redis中,对于一个存在问题的命令,如果在入队的时候就已经出错,整个事务内的命令将都不会被执行(其后续的命令依然可以入队),如果这个错误命令在入队的时候并没有报错,而是在执行的时候出错了,那么redis默认跳过这个命令执行后续命令。也就是说,redis只实现了部分事务。

      下面我们来看看刚刚提到的锁的问题,我们说过,redis的锁CAS(check and set)类似于乐观锁,redis的实现原理是使用watch进行监视一个(或多个)数据,如果在事务提交之前数据发生了变化(估计使用了类似于乐观锁的标记),那么整个事务将提交失败,我们可以举一个例子,我们开启两个终端,模拟两个人的操作,设置一条数据为count,初始时100,现在A对其进行监控,并且为count增加20。

      在没有提交之前,B也获取了这个count,为其减少50。

      那么这个时候A如果提交事务,会出现失败提示:

      可以看到,在A对数据的修改过程中,B对数据进行了修改,那么这条数据的“标记”就发生了变化,已经不是当初A取出数据的时候的标记了,这样,A的事务也就提交失败了。

     

      最后通过上述的实验,我们总结redis事务的三条性质:

      1. 单独的隔离操作:事务中的所有命令会被序列化、按顺序执行,在执行的过程中不会被其他客户端发送来的命令打断

      2. 没有隔离级别的概念:队列中的命令在事务没有被提交之前不会被实际执行

      3. 不保证原子性:redis中的一个事务中如果存在命令执行失败,那么其他命令依然会被执行,没有回滚机制

     

    3.Redis中的事务测试

      Redis事务可以一次执行多个命令,它先以MULTI开始一个事务, 然后将多个命令入队到事务中,最后由EXEC命令触发事务,一并执行事务中的所有命令。

      在传统的关系型数据中,只要有任意一条指令失败,则整个事务都会被撤销回滚,而在Redis中,中间某条指令的失败不会导致前面已做指令的回滚,也不会造成后续的指令不做,也因此得出 Redis 事务的执行并不是原子性的。

      也有以下命令参数执行错误的情况。

      还有watch锁,在事务中不能改变被锁的值。

     

    4.总结

      在redis中,对于一个存在问题的命令,如果在入队的时候就已经出错,整个事务内的命令将都不会被执行(其后续的命令依然可以入队),如果这个错误命令在入队的时候并没有报错,而是在执行的时候出错了,那么redis默认跳过这个命令执行后续命令。也就是说,redis只实现了部分事务。

      总结redis事务的三条性质:

      1.单独的隔离操作:事务中的所有命令会被序列化、按顺序执行,在执行的过程中不会被其他客户端发送来的命令打断

      2.没有隔离级别的概念:队列中的命令在事务没有被提交之前不会被实际执行

      3.不保证原子性:redis中的一个事务中如果存在命令执行失败,那么其他命令依然会被执行,没有回滚机制

     

    作者:小家电维修

    相见有时,后会无期。

  • 相关阅读:
    vs c++ 自动生成svn版本信息 编译前脚本
    cannot seek value-initialized vector iterator 程序崩溃
    shell 统计代码行数
    c++ 控制台程序增加图标的一种方法
    grpc c++ 设置断线重连时间
    拓端数据tecdat|R语言用lme4多层次(混合效应)广义线性模型(GLM),逻辑回归分析教育留级调查数据
    拓端数据tecdat|R语言计量经济学:虚拟变量(哑变量)在线性回归模型中的应用
    拓端数据tecdat|Python风险价值计算投资组合VaR(Value at Risk )、期望损失ES(Expected Shortfall)
    拓端数据tecdat|R语言用相关网络图可视化分析汽车配置和饮酒习惯
    拓端数据tecdat|R语言 PCA(主成分分析),CA(对应分析)夫妻职业差异和马赛克图可视化
  • 原文地址:https://www.cnblogs.com/lizexiong/p/14657341.html
Copyright © 2011-2022 走看看