Mysql锁

121 阅读6分钟

行锁

锁的粒度是最小的,发生锁冲突的概率也是最低的,所以并发性能最高。innodb的行锁是针对索引加的锁,而并非对整行记录加锁,这个取决于innodb的数据结构。

  • 行锁(record lock):锁定单条记录的锁,在读已提交(RC)和可重复读(RR)隔离级别下都是支持的。
    • 共享锁/读锁(简写S):共享锁兼容其它共享锁,但是不兼容排它锁。
    • 排它锁/写锁(简写X): 排它锁不兼容共享锁,排他锁不兼容自己。
      -- 加锁
      当我们执行insertdeleteupdate语句,以及select ... for update 的时候加的就是排它锁,事务提交之后会自动释放锁。
      
  • 间隙锁(cap lock):对两条记录的之间间隙加锁。锁住当前记录之前的间隙,加上间隙锁之后,可以保证在当前事务里面被锁住的间隙不变,防止其它事务在这个间隙插入数据,从而产生幻读现象。间隙锁在可重复读(RR)隔离级别下是支持的。间隙锁之间是相互兼容的。
  • 临键锁(next-key lock):行锁和间隙锁的组合,同时锁住数据,以及数据前的间隙,在可重复读(RR)级别下支持。

在默认的事务隔离级别下,Innodb会使用next-key(临键锁)进行扫描,防止出现幻读:

  • 在唯一索引下,针对已存在的记录进行等值匹配的时候,会自动将这个临建锁优化为行锁。
  • Innodb的行锁是针对索引加的锁,如果不通过索引字段查询,Innodb就会对表中所有的记录加行锁,也就类似行锁升级了表锁。
  • 唯一索引上的等值查询,给不存在的记录加锁时,会自动把临键锁优化为间隙锁。
  • 常规索引上的等值查询,向右遍历到第一个不满足查询条件的值,临键锁会自动退回间隙锁。
  • 对于范围查询(有索引),满足条件的值都会被加上临键锁,对应的主键会加上行锁(本身是主键除外)。

表锁

  • 表锁
    • 表共享读锁(read lock):所有的客户端都只能读取,其它客户端写入操作时均会阻塞。
      -- 加锁
      lock tables [表名1] [表名2] ... read;
      -- 释放锁
      unlock tables;
      
    • 表独占写锁(write lock): 当前客户端可以进行读写,其它客户端读写操作均会被阻塞。
      -- 加锁
      lock tables [表名1] [表名2] ... write;
      -- 释放锁
      unlock tables;
      
  • 元数据锁(meta data lock,简称MDL):系统进行控制,在我们访问表的时候,会自动加上,无需手动操作,为了避免DML与DDl的冲突,当一张表上面存在未提交的事务,其它事务是不可以修改元数据的,或者说不能修改表结构。当我们增删改查的时候,系统会自动加上MDL共享锁,当我们修改表结构的时候,系统会加上MDL排他锁。
--注意:元数据所是mysql 5.5.3引入的,但是performance_schema.metadate_locks这个表是5.7才引入的,默认关闭的,我们需要在配置文件中加一项设置,Windows 和 Linux 下的 MySQL 配置文件的名字和存放位置都是不同的,WIndows 下 MySQL 配置文件是 `my.ini` 存放在 MySQL 安装目录的根目录下;Linux 下 MySQL 配置文件是 `my.cnf` 存放在 `/etc/my.cnf`、`/etc/mysql/my.cnf`。在mysqld下面添加下面命令后保存退出。重启mysql服务。
performance_schema-instrument='wait/lock/metadata/sql/md1=ON'
-- mysql 8.0之后默认是打开的。
-- Mysql查看元数据锁命令
select * from performance_schema.metadate_locks\G;
  • 意向锁
-- 查看意向锁和行锁的加锁情况
  -- Mysql5.7.14之前使用下面命令查询,8.0之后被删除。
  select * from information_schema.innodb_locks;
  -- 8.0后
  select * from information_schema.data_locks;
  • 意向共享锁(IS):兼容表共享读锁,排斥表独占写锁
    -- 加行锁共享锁,事务提交之后会自动释放锁。
    select * from 表明 lock in share mode;
    
  • 意向排它锁(IX):排斥所有表锁,意向锁和意向锁之间是兼容的
    当我们执行insertdeleteupdate语句,以及select ... for update 的时候加的就是意向排它锁,事务提交之后会自动释放锁。
    

全局锁

全局锁:该锁锁的是整个数据库实例,这样就意味着其它客户端不可以对这个库的任何表进行写入操作,直到这个锁被释放。虽然不能写但是可以进行读取,因为全局锁期间,整个实例变为可读状态。使用场景:备份数据库。

-- 加全局锁
flush tables with read lock;
-- 释放全局锁
unlock tables;

死锁

当两个或者两个以上的事务相互等待对方释放资源的时候,就会发生死锁。
事务1 行1 行2(阻塞)
事务1 行2 行1(阻塞) 回滚

死锁案例

--事务A
-- 开启事务
begin;
update1 set name = 'A' where id = 1;
update1 set name = 'A' where id = 2;

--事务B
begin;
update1 set name = 'B' where id = 2;
update1 set name = 'B' where id = 1;

--Mysql检测到死锁会自动回滚事务B。

怎么避免死锁?

  1. 避免长事务,可以考虑将长事务分割为短事务,从而减少锁的持有时间。
  2. 尽量避免一个事务锁定多个资源。
  3. 尽量都按照统一的顺序请求锁。
  4. 如果允许出现幻读和不可重复读,可以使用读已提交(RC)隔离级别。

乐观锁

在并发场景下,多个事务可能会访问同一条记录,乐观锁会认为发生锁冲突的概率很低,所以在进行业务操作的时候,不会一开始就锁定数据,而是在提交数据更改的时候,检查当前数据是否被其他事务修改,如果没有就提交数据,否则就会回滚事务。

乐观锁是给每一条数据记录一个版本号/时间戳,当进行业务操作前,会获取当前数据版本号,然后根据实际更新数据时再次对比版本号,确认与之前获取的版本号是否相同。如果相同就可以确认这之间没有发生并发的修改,可以正常更新数据,如果版本对比不一致,则会任务该数据被并发修改过,需要回滚整个业务操作。然后根据需要重试整个过程。

注意:适用于冲突概率较低的场景。适用于读多写少的场景。

悲观锁

悲观锁认为发生锁冲突的概率很高,为了保证数据的完整性,在事务一开始读取的时候,就会一开始对这个数据进行加锁。

注意:适用于冲突概率较高的场景。如:写多读少的场景,比如强一致性的业务(银行,金融相关)。