关于mysql:MySQL-锁机制-悲观锁与乐观锁

12次阅读

共计 3456 个字符,预计需要花费 9 分钟才能阅读完成。

MySQL 的锁实现

  1. 乐观锁

    乐观锁(Pessimistic Locking),总是会很乐观的认为,每次去读数据的时候都认为他人会批改,所以每次在读数据的时候都会上锁,这样他人想读取数据就会阻塞直到它获取锁 (共享资源每次只给一个线程应用,其它线程阻塞,用完后再把资源转让给其它线程)。传统的关系型数据库里边就用到了很多乐观锁机制,比方行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。

  2. 乐观锁
    乐观锁(Optimistic Locking),总是会乐观的认为,每次去读数据的时候都认为他人不会批改,所以不会上锁,然而在更新的时候会判断一下在此期间有没有其余线程更新该数据,能够应用版本号机制和 CAS 算法实现。乐观锁实用于多读的利用类型,这样能够进步吞吐量。

    CAS 是英文单词 Compare and Swap 的缩写,翻译过去就是比拟并替换。CAS 机制中应用了 3 个根本操作数:内存地址 V,旧的预期值 A,要批改的新值 B。更新一个变量的时候,只有当变量的预期值 A 和内存地址 V 当中的理论值雷同时,才会将内存地址 V 对应的值批改为 B。

乐观锁应用

  1. 创立一个数据库

    DROP DATABASE IF EXISTS locktest;
    CREATE DATABASE locktest;
    
    USE locktest;
    
    DROP TABLE IF EXISTS warehouse;
    
    CREATE TABLE IF NOT EXISTS warehouse (
      id      INTEGER NOT NULL,
      stock   INTEGER DEFAULT 0,
      version INTEGER DEFAULT 1,
      PRIMARY KEY (id)
    )
      ENGINE = INNODB;
    
    INSERT INTO warehouse VALUE (1, 200, 1);
    
    SELECT * FROM warehouse;
    id stock version
    1 200 1
  2. 关上 2 个 MySQL 终端,并敞开主动提交:set autocommit = 0;

    autocommit 只对反对事务的引擎起作用,如 InnoDB,默认状况下 autocommit 的值为 1,MySQL 默认对写操作会主动开启事务,autocommit = 1 时会在操作执行 commit 提交操作

    set autocommit = 0;
  3. 第 1 个终端执行查问 这里咱们应用 for update 关键字给表上锁

    select * from warehouse where id = 1 for update; 
    mysql> select * from warehouse where id = 1 for update; 
    +----+-------+---------+
    | id | stock | version |
    +----+-------+---------+
    |  1 |     0 |       1 |
    +----+-------+---------+
     1 row in set (0.04 sec)
    
  4. 这时咱们在第 2 个终端也执行一次查问,会发现查问被挂起了

    mysql> select * from warehouse where id = 1 for update; 
    ............ 有限期待中................
  5. 这里是因为咱们首先敞开了主动提交,第 1 个终端的查问操作没有提交,这时候在第 2 个终端执行的查问语句会挂起,期待前一个操作提交
  6. 咱们把第 1 个终端的操作更新提交了

    update warehouse set stock = stock - 1 where id = 1;
    commit; 
  7. 咱们会发现第 2 个终端的查问终于执行了,咱们能够发现这个操作被挂起了 15 秒,直到前一个操作提交了事务

    mysql> select * from warehouse where id = 1 for update; 
    +----+-------+---------+
    | id | stock | version |
    +----+-------+---------+
    |  1 |   199 |       1 |
    +----+-------+---------+
     1 row in set (15.32 sec)
    
    mysql> 
  8. 这里咱们能够得出个论断,在执行 A 操作时,MySQL 乐观锁能够阻止 B 操作,直至第一次操作提交事务,B 操作才会被执行,理论应用能够解决一些商城超买的并发问题(当然,高并发必定要应用 Redis 等缓存服务器,不然以数据库的性能和速度齐全不够用)

高并发测试

  1. 筹备接口

    • 咱们这里应用 PHP 编写一个抢购的接口

      $dsn = array(
      'host' => '127.0.0.1',         // 设置服务器地址
      'port' => '3306',              // 设端口
      'dbname' => 'locktest',             // 设置数据库名
      'username' => 'root',           // 设置账号
      'password' => 'root',      // 设置明码
      'charset' => 'utf8',             // 设置编码格局
      'dsn' => 'mysql:host=127.0.0.1;dbname=locktest;port=3306;charset=utf8',
      );
      // 连贯
      $options = array(PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, // 默认是 PDO::ERRMODE_SILENT, 0, (疏忽错误模式)
      PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, // 默认是 PDO::FETCH_BOTH, 4
      );
      
      try{$db = new PDO($dsn['dsn'], $dsn['username'], $dsn['password'], $options);
      }catch(PDOException $e){die('数据库连贯失败:' . $e->getMessage());
      }
      
      
      $list = $db->query('SELECT * FROM `warehouse` WHERE `id` = 1')->fetchAll();
      if ($list[0]['stock'] >= 1)
      {$db->exec("UPDATE `warehouse` SET `stock` = `stock` - 1 WHERE `id` = 1");
      print_r("买到了一个");
      }else{print_r("库存没有了:" . $list[0]['stock']);
      }
      
  2. 装置 jmeter

    • JMeter 是 Apache 组织开发的基于 Java 的压力测试工具。用于对软件做压力测试,它最后被设计用于 Web 利用测试,但起初扩大到其余测试畛域。
    • 下载 jmeter
    • 点击这个下载
    • 将 jmeter 解压到一个中央,进入 bin 目录,关上 jmeter.sh 或者 jmeter.bat 都能够(sh 须要当时装置 sh 脚本解释器,bat 则应用 cmd 关上)
    • 关上来是这样子的
    • 右击增加一个线程组
    • 在增加一个 http 申请
    • 增加一个查问后果树
    • 咱们这里能够设置并发,线程数设置 500,意思是同一时间内 500 条线程同时拜访接口,循环次数设置为 5。意思是每个线程循环拜访 5 次
    • 将方才咱们的接口填入 HTTP 申请外面
  3. 开始测试

    • 点击绿色的开始按钮
    • 这里如果第一次点击开始,会提醒你是否保留 jmx 配置文件,咱们找个中央保留即可
    • 查看后果
    • 咱们发现库存变成正数了,这是不被容许的,如果这是个商城,超买了是会承当不少损失的,事实上咱们在接口里进行了库存判断,然而因为是并发执行,所以会存在同时查询数据库,呈现超买的问题
    • 在接口外面的查问 SQL 里应用乐观锁,这里须要开启事务
      批改后:

      // 开启事务
      $db->beginTransaction();
      
      $list = $db->query('SELECT * FROM `warehouse` WHERE `id` = 1 FOR UPDATE')->fetchAll();
      if ($list[0]['stock'] >= 1)
      {$db->exec("UPDATE `warehouse` SET `stock` = `stock` - 1 WHERE `id` = 1");
      print_r("买到了一个");
      $db->commit();}else{print_r("库存没有了:" . $list[0]['stock']);
      }
      
    • 咱们先把数据库里的库存加回 200,在试一遍,咱们会发现超买的问题解决了
  4. 应用乐观锁的论断

    • 咱们在接口外面的 SELECT 查问里应用表锁 锁定了库存表,这样会让其他人先停下来,等到咱们把库存 -1,在提交事务后,其他人能力进行查问,这样就避免了超买的问题
    • 能够晓得每次查问都会锁表,所以在多读的场景下乐观锁会有额定不必要的开销,更适宜多写的环境
    • 因为乐观锁会很王道的锁表,所以如果锁表的接口呈现问题,会导致后续的查问永远挂起,造成死锁的状况
    • 锁表只能在反对事务的数据库引擎下应用
    • 高并发如秒杀流动之类的场景,举荐应用 Redis 等缓存服务器

正文完
 0