thinkphp中的lock与mysql的for update的使用注意事项

一、数据库需要锁表的场景

  1. 场景1,对于资金和商品数量,非常重要。在同一时间内必须只有一个连接能够修改。
  2. 场景2:一个表A的值,依赖于表B的值,那么我在计算A更新值时,必须保证这个时间内,B表的值没有发生变化

二、 在thinkphp框架锁表的实现

  1. 使用模型类,调用lock()方法, 参数true是全局范围的,false只是当前链接

  2. lock()调用会在sql语句后面,加上“for update”.

  3. lock生效的条件,必须方式一个事务中,可以使用所有表开启Db::stratTrans(),Db::commit()之后锁表结束

    • //单个表事务
      $user = new User();
      $db = $user->db(true);
      $db->startTrans();
      $res = $user->lock(true)->where(["id" => 1])->field('mobile,username')->find();
      sleep(3); // 休眠3秒,ID=1的行会锁表3秒
      $db->commit();
      
    • //多个表事务
      Db::stratTrans();
      User::lock(true)->where(["id" => 1])->field('mobile,username')->find();
      sleep(3); // 休眠3秒,ID=1的行会锁表3秒
      Db::commit();//或者回退也是户释放的,Db:rollback();
      
  4. lock生效过程中,其他的并发请求中的Update操作都会出于阻塞,等待的状态

  5. lock使用的注意事项,lock的条件,一定要用主键唯一索引键,否则容易导致,整个表被锁住,而导致其他服务都出于等待中。原因,下面会讲。

三、mysql中for update

  1. for update是在数据库中上锁用的,可以为数据库中的行上一个排它锁。当一个事务的操作未完成时候,其他事务可以读取但是不能写入或更新。

  2. InnoDB默认是行级别的锁,当有明确指定的主键时候,是行级锁。否则是表级别。

  3. for update 仅适用于InnoDB,并且必须开启事务,在begin与commit之间才生效。

  4. 锁的具体实例,假设表foods ,存在有id跟name、status三个字段,id是主键,status有索引。

    1. 明确指定主键,并且有此记录,行级锁

      SELECT * FROM foods WHERE id=1 FOR UPDATE;
      SELECT * FROM foods WHERE id=1 and name=’咖啡色的羊驼’ FOR UPDATE;
      
    2. 明确指定主键/索引,若查无此记录,无锁

      SELECT * FROM foods WHERE id=-1 FOR UPDATE;
      
    3. 无主键/索引,表级锁。影响非常大

      SELECT * FROM foods WHERE name=’咖啡色的羊驼’ FOR UPDATE;
      
    4. 主键/索引不明确,表级锁。影响非常大

      SELECT * FROM foods WHERE id<>’3’ FOR UPDATE;
      SELECT * FROM foods WHERE id LIKE ‘3’ FOR UPDATE;
      
  5. for update nowait 锁住表或者锁住行,只允许当前事务进行操作(读写),其他事务被拒绝,事务占据的statement连接也会被断开

posted @ 2021-05-29 18:47  千年寒冰火  阅读(1883)  评论(0编辑  收藏  举报