详解mysql的全局锁、表锁、行锁

首先先从表锁开始

顾名思义,会锁整张表,mysql基本的策略,不依赖存储引擎,粒度比较大,开销小,很好的避免死锁问题

这就会引发一个问题,力度大,就意味着锁资源争用概率高,并发能力弱

表锁又可分为:表级别的S锁和X锁、意向锁、元数据锁、自增锁

接下来我们逐个介绍

① 表级别的S锁、X锁

一般情况下,不会使用到InnoDB中提供的表级别的S锁和X锁,只会在一些特殊情况下,比方说崩溃恢复过程中用到;在MyISM引擎比较常用。

  • 使用MyISM执行select语句前,给所有涉及到的表加读锁,在执行增删改操作前,会给所有的数据加写锁
  • 对某个表执行CRUL时,InnoDB存储引擎不会给表添加表级别的S锁和X锁

手动 获取 InnoDB存储引擎提供的表t 的 S锁 或者 X锁:

* LOCK TABLES t READ :InnoDB存储引擎会对表 t 加表级别的 S锁 。
* LOCK TABLES t WRITE :InnoDB存储引擎会对表 t 加表级别的 X锁 。

-- 解锁
UNLOCK TABLES; 应尽量避免在InnoDB存储引擎的表上使用 LOCK TABLES 这样的手动锁表语句

 

 ② 意向锁 (intention lock)

意向锁的作用就是加快表锁的检查过程。

  • 意向共享锁(intention shared lock, IS):在使用 InnoDB 引擎的表里对某些记录加上「共享锁」之前,需要先在表级别加上一个「意向共享锁」;

-- 事务要获取某些行的 S 锁,必须先获得表的 IS 锁。
SELECT column FROM table ... LOCK IN SHARE MODE; 

  • 意向排他锁(intention exclusive lock, IX):在使用 InnoDB 引擎的表里对某些纪录加上「独占锁」之前,需要先在表级别加上一个「意向独占锁」;

-- 事务要获取某些行的 X 锁,必须先获得表的 IX 锁。
SELECT column FROM table ... FOR UPDATE;

eg1:

事务1首先获取行级别的共享锁(在这之前会先获取该表的意向共享锁),事务2想获取该表的表级别的共享锁,先检查该表的意向锁,发现是意向共享锁,因为是兼容的,所以加表锁成功。

eg2:

事务1获取行级别的排他锁(在这之前会先获取该表的意向排他锁),事务2想获取该表的表级别的共享锁,先检查该表的意向锁,发现是意向排他锁,读锁和写锁不兼容,所以加表锁失败,阻塞等待。 

 关系图如下

查询意向锁如下

-- 查看意向锁(还有其他类型的锁)
select OBJECT_SCHEMA,OBJECT_NAME,INDEX_NAME,LOCK_TYPE,LOCK_MODE,LOCK_DATA from performance_schema.data_locks;

总结:意向锁的目的是为了快速判断表里是否有记录被加锁

 ③ 自增锁(AUTO-INC锁)

自增锁是一种特殊的表级别锁,专门针对事务插入AUTO_INCREMENT类型的列。

不是再一个事务提交后才释放,而是再执行完插入语句后就会立即释放

再插入一个数据时,会加一个表级别的Auto-inc锁,插入完成时,就会立即释放

一个事务在持有 AUTO-INC 锁的过程中,其他事务的如果要向该表插入语句都会被阻塞,从而保证插入数据时,被 AUTO_INCREMENT 修饰的字段的值是连续递增的。

④ 元数据锁(MDL锁)

MySQL5.5引入了meta data lock,简称MDL锁,属于表锁范畴。

  • 对一张表进行 CRUD 操作时,加的是 MDL 读锁
  • 对一张表做结构变更操作的时候,加的是 MDL 写锁

 

-- 查看元数据锁
select OBJECT_TYPE,OBJECT_SCHEMA,OBJECT_NAME,LOCK_TYPE, LOCK_DURATION from performance_schema.metadata_locks;

 行锁

需要的注意的是,MySQL server层并没有实现 行锁机制,行级锁只在存储引擎层实现

优点:锁粒度小,锁冲突的概率小,并发度高

缺点:开销大,上锁慢,容易死锁

① 记录锁(Record Locks

记录锁就是行级别的X锁和S锁,仅仅锁住一记录,分S型记录锁X型记录锁 官方的类型名称为: LOCK_REC_NOT_GAP

eg1:

需要特别注意的是加锁的执行过程中所有扫描到的行都会被锁上,因此必须确定条件使用了索引,这样才能精准锁定,而如果没有索引,会进行全表扫描,那么就会锁住无关紧要的数据。

查询锁语句

select object_schema,object_name,index_name,lock_type,lock_mode,lock_data from performance_schema.data_locks;

(1)针对唯一索引进行检索时,对已存在的记录进行等值匹配时,将会自动优化为行锁。
(2)INNODB的行锁是针对于索引加的锁,不通过索引条件检索数据,那么INNODB将对表中的所有记录加锁,此时就会升级为表锁。

② 间隙锁(Gap Locks)

 图中id值为8的记录加了gap锁,意味着不允许别的事务在(5,8)之间插入新记录。比如,有另外一个事务再想插入一条id值为6的新记录,它定位到该条新记录的下一条记录的id值为8,而这条记录上又有一个gap锁,所以就会阻塞插入操作,直到拥有这个gap锁的事务提交了之后,id列的值在区间(5, 8)中的新记录才可以被插入。

gap锁的提出仅仅是为了防止插入幻影记录而提出的,没有额外其他功能。

间隙锁容易发生死锁

事务1和事务2都有某个记录的间隙锁,此时事务2因为插入记录而被阻塞(阻塞原因是事务1的间隙锁),所以事务2需要等待事务1提交,然而事务1试图插入记录,插入的记录在事务2中被间隙锁锁住了,所以事务1会去等待事务2提交,这也就出现了死锁,互相持有对方的锁。

select必须要加锁(for share、for update)才能解决幻读问题
③临键锁(Next-Key Locks) 

有时候我们既想锁住某条记录 ,又想阻止其他事务在该记录前边的间隙插入新记录,所以InnoDB就提出了一种称之为Next-Key Locks的锁 。官方的类型名称为: LOCK_ORDINARY。我们也可以简称为 next-key锁

使用条件:临键锁是在存储引擎InnoDB、事务级别在可重复读 的情况下使用的数据库锁, InnoDB默认的锁就是Next-Key locks。

临键锁 = 记录锁 + 间隙锁

eg1:

-- 给id小于等于8的所有记录加上临键锁
select * from student where id<=8 for update;


-- 给id为[6,8]的记录加上临键锁
select * from student where id<=8 and id>6 for update;

 

如何理解InnoDB默认的锁就是Next-Key locks? 

 加锁的对象是索引,加锁的基本单位是 next-key lock,防止幻读。但是在一些场景下,InnoDB会将它优化为记录锁或间隙锁:

  1. 唯一索引作为等值查询的条件,给存在的记录加锁时,会优化为行锁。
  2. 唯一索引作为等值查询的条件,给不存在的记录加锁时,会优化为间隙锁(对应前面的例子)。

接下来,如果有其他事务插入 id 值为 2、3、4 这一些记录的话,这些插入语句都会发生阻塞。

注意,如果其他事务插入的 id = 1 或者 id = 5 的记录话,并不会发生阻塞,而是报主键冲突的错误,因为表中已经存在 id = 1 和 id = 5 的记录了。

原因就是在唯一索引等值查询并且查询记录不存在的场景下,仅靠间隙锁就能避免幻读的问题。

  • 为什么 id = 5 记录上的主键索引的锁不可以是 next-key lock?如果是 next-key lock,就意味着其他事务无法删除 id = 5 这条记录,但是这次的案例是查询 id = 2 的记录,只要保证前后两次查询 id = 2 的结果集相同,就能避免幻读的问题了,所以即使 id =5 被删除,也不会有什么影响,那就没必须加 next-key lock,因此只需要在 id = 5 加间隙锁,避免其他事务插入 id = 2 的新记录就行了。
  • 为什么不可以针对不存在的记录加记录锁?锁是加在索引上的,而这个场景下查询的记录是不存在的,自然就没办法锁住这条不存在的记录。

总结:

  1. 索引上的等值查询,向右遍历时且最后一个不满足等值条件,会将临键锁优化为间隙锁
  2. 非唯一索引等值查询:如果锁定读查询语句,没有使用索引列作为查询条件,或者查询语句没有走索引查询,导致扫描是全表扫描。那么,每一条记录的索引上都会加 next-key 锁,这样就相当于锁住的全表,这时如果其他事务对该表进行增、删、改操作的时候,都会被阻塞
  3. 唯一\普通索引做范围查询时,扫描到的都会加临键锁,注意不会优化为间隙锁
  4. 如果锁定读查询语句,没有使用索引列作为查询条件,或者查询语句没有走索引查询,导致扫描是全表扫描。那么,每一条记录的索引上都会加 next-key 锁,这样就相当于锁住的全表,这时如果其他事务对该表进行增、删、改操作的时候,都会被阻塞

④插入意向锁(Insert Intention Locks)

一个事务在插入一条记录时需要判断一下插入位置是不是被别的事务加了间隙锁,如果有的话,插入操作需要等待,直到有间隙锁的那个事务提交。但是InnoDB规定事务在等待的时候也需要在内存中生成一个锁结构,表明有事务想在某个间隙中插入新记录,但是现在在等待。InnoDB就把这种类型的锁命名为插入意向锁 。插入意向锁是一种Gap锁,不是意向锁,在insert操作时产生

插入意向锁是在插入一条记录行前,由INSERT操作产生的一种间隙锁 。 事实上插入意向锁并不会阻止别的事务继续获取该记录上任何类

全局锁

全局锁就是对 整个数据库实例 加锁。当你需要让整个库处于 只读状态 的时候,可以使用这个命令,之后 其他线程的以下语句会被阻塞:数据更新语句(数据的增删改)、数据定义语句(包括建表、修改表结 构等)和更新类事务的提交语句。

全局锁的典型使用 场景 是:做全库逻辑备份

Flush tables with read lock
unlock tables

因为在可重复读的隔离级别下,即使其他事务更新了表的数据,也不会影响备份数据库时的 Read View(以后会讲),这就是事务四大特性中的隔离性,这样备份期间备份的数据一直是在开启事务时的数据。

备份数据库的工具是 mysqldump,在使用 mysqldump 时加上 –single-transaction 参数的时候,就会在备份数据库之前先开启事务。这种方法只适用于支持「可重复读隔离级别的事务」的存储引擎。

 

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

郎伟学架构

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值