MySQL 锁机制 悲观锁与乐观锁

MySQL 锁机制 悲观锁与乐观锁_第1张图片

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
    • 点击这个下载
      MySQL 锁机制 悲观锁与乐观锁_第2张图片
    • 将jmeter解压到一个地方,进入bin目录,打开jmeter.sh或者jmeter.bat都可以(sh需要事先安装sh脚本解释器,bat则使用cmd打开)
      MySQL 锁机制 悲观锁与乐观锁_第3张图片
    • 打开来是这样子的
      MySQL 锁机制 悲观锁与乐观锁_第4张图片
    • 右击添加一个线程组
      MySQL 锁机制 悲观锁与乐观锁_第5张图片
    • 在添加一个http请求
      MySQL 锁机制 悲观锁与乐观锁_第6张图片
    • 添加一个查询结果树
      MySQL 锁机制 悲观锁与乐观锁_第7张图片
    • 我们这里可以设置并发,线程数设置500,意思是同一时间内500条线程同时访问接口,循环次数设置为5。意思是每个线程循环访问5次
      MySQL 锁机制 悲观锁与乐观锁_第8张图片
    • 将刚才我们的接口填入HTTP请求里面
      1.png
  3. 开始测试

    • 点击绿色的开始按钮
      MySQL 锁机制 悲观锁与乐观锁_第9张图片
    • 这里如果第一次点击开始,会提示你是否保存jmx配置文件,我们找个地方保存即可
    • 查看结果
      MySQL 锁机制 悲观锁与乐观锁_第10张图片
    • 我们发现库存变成负数了,这是不被允许的,如果这是个商城,超买了是会承担不少损失的,事实上我们在接口里进行了库存判断,但是由于是并发执行,所以会存在同时查询数据库,出现超买的问题
      MySQL 锁机制 悲观锁与乐观锁_第11张图片
    • 在接口里面的查询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,在试一遍,我们会发现超买的问题解决了
      MySQL 锁机制 悲观锁与乐观锁_第12张图片
  4. 使用悲观锁的结论

    • 我们在接口里面的SELECT查询里使用表锁 锁定了库存表,这样会让其他人先停下来,等到我们把库存-1,在提交事务后,其他人才能进行查询,这样就防止了超买的问题
    • 可以知道每次查询都会锁表,所以在多读的场景下悲观锁会有额外不必要的开销,更适合多写的环境
    • 因为悲观锁会很霸道的锁表,所以如果锁表的接口出现问题,会导致后续的查询永远挂起,造成死锁的情况
    • 锁表只能在支持事务的数据库引擎下使用
    • 高并发如秒杀活动之类的场景,推荐使用Redis等缓存服务器

你可能感兴趣的:(mysqlmysql优化)