• Redis笔记之Redis事务


    1. 事务

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

    Redis事务本质:一组命令的集合
    步骤:

    1. 开启事务
    2. 操作一组命令(所有命令放入一个队列)
    3. 执行事务

    事务中每条命令都会被序列化,执行过程中按顺序执行,不允许其他命令的干扰。

    • 一致性
    • 顺序性
    • 排他性
    • Redis事务没有隔离级别的概念

    1.1 Redis事务操作过程

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

    1.1.1 事务的开启与执行

    所有事务中的命令在加入时都没有被执行,直到提交时才会开始执行(exec)一次性完成。

    案例:

    127.0.0.1:6379> multi # 开启事务
    OK
    127.0.0.1:6379> set k1 v1 # 命令入队
    QUEUED
    127.0.0.1:6379> set k2 v2
    QUEUED
    127.0.0.1:6379> get k1
    QUEUED
    127.0.0.1:6379> set k3 v3
    QUEUED
    127.0.0.1:6379> keys *
    QUEUED
    127.0.0.1:6379> exec # 执行事务
    1) OK
    2) OK
    3) "v1"
    4) OK
    5) 1) "k3"
       2) "k2"
       3) "k1"
    127.0.0.1:6379> 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    1.1.2 取消事务(discard)

    案例:

    127.0.0.1:6379> multi # 开启事务
    OK
    127.0.0.1:6379> set k1 v1 # 命令入队
    QUEUED
    127.0.0.1:6379> set k2 v2
    QUEUED
    127.0.0.1:6379> discard # 放弃事务
    OK
    127.0.0.1:6379> exec
    (error) ERR EXEC without MULTI  # 当前未开启事务,无法执行
    127.0.0.1:6379> get k1 # 被放弃事务中的命令并未执行
    (nil)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    1.2 事务错误

    1.2.1 编译时异常

    代码语法错误(编译时异常)所有的命令都不执行

    案例:

    127.0.0.1:6379> multi # 开启事务
    OK
    127.0.0.1:6379> set k1 v1 # 命令入队
    QUEUED
    127.0.0.1:6379> set k2 v2
    QUEUED
    127.0.0.1:6379> key * # 遇到语法错误异常
    (error) ERR unknown command `key`, with args beginning with: `*`, 
    127.0.0.1:6379> get k2
    QUEUED
    127.0.0.1:6379> exec # 提交事务失败
    (error) EXECABORT Transaction discarded because of previous errors.
    127.0.0.1:6379> get k1 # 所有命令都不会被执行
    (nil)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    1.2.2 运行时异常

    代码逻辑错误(运行时异常其他命令可以正常执行 >>> 所以不保证事务的原子性

    案例:

    127.0.0.1:6379> multi # 开启事务
    OK
    127.0.0.1:6379> set k1 v1 # 命令入队
    QUEUED
    127.0.0.1:6379> set k2 v2
    QUEUED
    127.0.0.1:6379> incr k1 # 逻辑错误(对字符串无法进行增量)
    QUEUED
    127.0.0.1:6379> get k2
    QUEUED
    127.0.0.1:6379> exec
    1) OK
    2) OK
    3) (error) ERR value is not an integer or out of range # 运行时报错
    4) "v2" # 其他命令正常执行
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    虽然中间有一条命令报错了,但是后面的指令依旧执行成功了,所以所Redis单条命令保证原子性,但是Redis不能保证原子性。

    总结:

    1. 编译时异常:所有命令都不会被执行
    2. 运行时异常:错误命令不会被执行,其他命令正常执行

    1.3 监控

    1.3.1 悲观锁

    1. 概念
      悲观锁,顾名思义:很悲观,在每次事务当中,都认为有人会更改数据,所以提前把数据锁住,一种以悲观的态度来防止一切数据冲突。

    在修改数据之前把数据锁住,然后对数据进行读写,在它释放锁之前任何人都不能对其数据进行操作,直到前面一个人把锁释放后,下一个人对数据加锁才能操作数据
    一般数据库本身锁的机制都是基于悲观锁的机制来实现的

    1. 特点
      可以完全保证数据的独占性和正确性,因为每次请求都会对数据进行加锁,然后进行数据操作,最后再解锁,而加锁解锁的过程会造成消耗,所以性能不高;
    2. 实现
      • mysql 悲观锁的方法:

        1. 开启事务:

          start transaction;
          
          • 1
        2. 查询信息

          select status from table where id = 1 for update  # for update加锁
          
          • 1
        3. 根据信息生成订单

          insert into table111(id, goods_id) vlaues(null,1);
          
          • 1
        4. 修改商品

          update table set status = 2 where id =1;
          
          • 1
        5. 提交信息

          commit; # commit释放锁
          
          • 1
      • Java悲观锁的实现

        • Java 中悲观锁的实现包括 synchronized 关键字和 Lock 相关类等,我们以 Lock 接口为例,例如 Lock 的实现类 ReentrantLock,类中的 lock() 等方法就是执行加锁,而 unlock() 方法是执行解锁。处理资源之前必须要先加锁并拿到锁,等到处理完了之后再解开锁。

    1.3.2 乐观锁

    1. 概念

      • 很乐观,认为什么时候都不会出现问题,所以不会上锁!在更新数据的时候判断一下,在此期间是否有人修改过这个数据
      • 获取version
      • 更新的时候比较version
    2. 实现

      1. 给表添加version字段,模拟两个线程,线程A和线程B同时执行修改一条数据
      2. 线程A执行更新
        update table set goods='goods_1',version=version+1 where id = 1 and version = 0;
        
        • 1
      3. 线程B执行更新,此时数据已经被修改了,所以线程B执行不成功
        update table set goods='goods_1',version=version+1 where id = 1 and version = 0;  #   判断version,是否有人修改过这个数据
        
        • 1
      4. 实现乐观锁控制

      如果不使用乐观锁控制,AB同时操作一条数据,会造成一条数据被修改了两次,产生脏数据,例如买票,只剩下一张票,结果AB都拿到了票,数据库发现票数为-1的情况。

    1.3.3 监控数据(watch key)

    使用watch key 监控指定数据,相当于乐观锁加锁

    正常执行

    127.0.0.1:6379> set money 100 # 设置余额:100
    OK
    127.0.0.1:6379> set use 0 # 支出使用:0
    OK
    127.0.0.1:6379> watch money # 监控money(上锁)
    OK
    127.0.0.1:6379> multi # 开启事务
    OK
    127.0.0.1:6379> decrby money 20
    QUEUED
    127.0.0.1:6379> incrby use 20
    QUEUED
    127.0.0.1:6379> exec # 监视值没有被被中途修改,事务正常执行
    1) (integer) 80
    2) (integer) 20
    127.0.0.1:6379>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    测试多线程修改值,使用watch可以当作redis的乐观锁操作( 相当于getversion )

    启动另外一个客户端模拟插队线程

    • 线程1:

      # money: 100 use: 0
      127.0.0.1:6379> watch money # 上锁
      OK
      127.0.0.1:6379> multi
      OK
      127.0.0.1:6379> decrby money 20
      QUEUED
      127.0.0.1:6379> incrby use 20
      QUEUED
      127.0.0.1:6379>  # 此时事务还没有结束,还没执行事务
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
    • 线程2插队:

      127.0.0.1:6379> incrby money 500 # 修改了线程1中监视的money
      (integer) 600
      127.0.0.1:6379> 
      
      • 1
      • 2
      • 3
    • 回到线程1,执行事务:

      127.0.0.1:6379> exec # 执行之前,另外一条线程修改了money,这个时候就会导致事务执行失败
      (nil) # 没有结果,说明事务执行失败
      127.0.0.1:6379> get money # 线程2,修改生效
      "600"
      127.0.0.1:6379> get use # 线程1事务执行失败,数值没有修改
      "0"
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6

    解锁(unwatch

    unwatch进行解锁获取最新值,然后再加锁进行事务。

    注意
    每次提交执行exec后都会自动释放锁,不管是否成功

  • 相关阅读:
    测试/开发程序员的级别“陷阱“,级别不是衡量单维度的能力......
    【Spark的五种Join策略解析】
    牛客网开源1240页字节算法实录,无意中掀起GitHub刷题热潮
    SparkCore系列-8、RDD 读写外部数据源
    专业128分总分390+上岸中山大学884信号与系统电通院考研经验分享
    二叉树练习
    C#:实现Google S2算法(附完整源码)
    D365 根据选中数据行的字段值,控制按钮是否可点击
    性能测试分析与使用
    【LeetCode】Day147-找到字符串中所有字母异位词
  • 原文地址:https://blog.csdn.net/qq_45609369/article/details/126567666