笔者思考:


在我看来,细化的锁知识,是为了应付并发场景下可能会出现的数据问题、效率问题。正是为了追求效率的最大化(并发量的最大化),才将原本一个个简单粗暴的锁,改良为一个个精良的锁。以至于出现了越来越多的知识点。

这不禁让我联想到了算法,暴力依然可以解决问题,但是它不够漂亮、不够优美,于是衍生出了许多特定算法。

相比较于工作中的拿来主义、开箱即用,我更倾向于将编程之美理解为用代码设计出优美、精良的东西,尽管我也知道我目前的能力还不足以达到这样的目的…


文章目录

  • ​​一、行级别​​
  • ​​1、共享锁/S锁/读锁(Shared Locks)​​
  • ​​2、排他锁/X锁/写锁(Exclusive Locks)​​
  • ​​3、记录锁(Record Locks)​​
  • ​​4、间隙锁(Gap Locks)​​
  • ​​5、临键锁(Next-Key Locks)​​
  • ​​二、表级别​​
  • ​​1、意向共享锁/IS锁(Intention Shared Locks)​​
  • ​​2、意向排他锁/IX锁(Intention Exclusive Locks)​​
  • ​​3、意向锁作用​​
  • ​​三、补充锁​​
  • ​​1、插入意向锁(Insert Intention Locks)​​
  • ​​2、自增锁(AUTO-INC Locks)​​
  • ​​3、空间锁(Predicate Locks for Spatial Indexes)​​


InnoDB存储引擎内常见锁的分类定位

浅谈InnoDB存储引擎下锁的分类_共享锁


一、行级别

注意:InnoDB中行级锁是基于索引实现的,即需要命中索引,才能生效。当我们使用sql语句做查询操作,命中索引则添加行锁,此时,共享指定行;没有命中索引则添加表锁,则共享整张表



表中的数据:

浅谈InnoDB存储引擎下锁的分类_意向锁_02



1、共享锁/S锁/读锁(Shared Locks)



对某一资源加共享锁,自身可以读该资源,其他人也可以读该资源(其他人在读资源时,也会添加对应的共享锁。即多个共享锁可以共存)。但是其他人无法修改该资源,想要修改就必须等所有的共享锁都释放完之后才能进行。

-- 加共享锁锁方式
select * from student lock in share mode;

--释放锁(提交事务或回滚事务)
COMMIT;
ROLLBACK;



示例:

①共享锁 + 共享锁:共享锁能够互存
对同一条数据记录添加共享锁

浅谈InnoDB存储引擎下锁的分类_数据_03


②共享锁 + 排他锁:对加了共享锁的表,无法再添加排他锁

对同一条数据记录先添加共享锁,再添加排他锁

浅谈InnoDB存储引擎下锁的分类_意向锁_04


2、排他锁/X锁/写锁(Exclusive Locks)

对某一资源加排他锁,自身可以进行增删改查,其他人无法进行任何操作。即排他锁与其他锁不能共存

-- 加排他锁的方式
-- DML语句默认会加上排他锁
select * from student for update -- 手动修改数据的sql语句


--释放锁(提交事务或回滚事务)
COMMIT;
ROLLBACK;



①排他锁 + 共享锁:对加了排他锁的表,无法再添加共享锁

先对表中的同一条数据记录先添加排他锁,再添加共享锁

浅谈InnoDB存储引擎下锁的分类_意向锁_05



②排他锁 + 排他锁:对加了排他锁的表,无法再添加排他锁

对表中的同一条数据先添加排他锁,再添加排他锁

浅谈InnoDB存储引擎下锁的分类_共享锁_06




③自己添加了排他锁以后,自身依然可以增删改查

对表中的同一个数据先添加排他锁,再添加共享锁,对应操作依然能够执行

浅谈InnoDB存储引擎下锁的分类_意向锁_07



由此我们可以得出共享锁和排他锁的关系:

(2)排他锁/X

(2)共享锁/S

(1)排他锁/X

冲突

冲突

(1)共享锁/S

冲突

兼容



按网上的说法,我这里的测试结果是有问题的。在共享锁的基础上,是可以添加排他锁;在排他锁的基础上,也是可以添加共享锁。这会引发InnoDB数据的另一个概念版本并发控制MVCC(Multi-Version Concurrency Control),这里就不再引申。

但是,但是问题的关键是,我没有触发这个东西(索引也加了),这就让我心情很复杂。我也不知道到底是谁对,求路过的小伙伴解答!!!当然前文的表格中的结论是没有问题的


后来和同学讨论了一下,得出一个结论,不知道正确与否,请各位看官讲解讲解:
InnoDB为了更好的支持和处理并发问题,所以对常规的select语句都不再添加共享锁,但是在我演示的中共享锁+排他锁(读+写)、排他锁+共享锁(写+读)例子中,均是显示的添加了共享锁,以至于让冲突再次发生。



3、记录锁(Record Locks)

记录锁存在于必须是精准命中索引并且索引是唯一索引,根据条件,直接对某行记录进行加锁。

-- 对id = 3的数据添加记录锁
select * from student where id = '3' for update

注意:

id列必须为唯一索引列或主键列,否则上述语句加的锁就会变成临键锁。

同时查询语句必须为精准匹配,不能为 >、<、like等,否则也会退化成临键锁


4、间隙锁(Gap Locks)

间隙锁基于非唯一索引,它锁定一段范围内的索引记录(必须命中索引)。使用间隙锁锁住的是一个区间,而不仅仅是这个区间中的数据。



根据我们数据库表中已有的数据间隙,对表进行间隙的划分

浅谈InnoDB存储引擎下锁的分类_意向锁_08


上图的SQL语句,转换为图的形式为

当我们进行范围锁定时,看似我们锁定的是id > '21’的部分,但由于间隙锁的存在,实际上我们锁定的是(8,+∞],以至于我们添加数据的id = 9时,两个操作冲突,无法进行。

浅谈InnoDB存储引擎下锁的分类_数据_09


5、临键锁(Next-Key Locks)

mysql的行锁默认就是使用的临键锁,临键锁是由记录锁和间隙锁共同实现的。

临键锁可以理解为一种特殊的间隙锁,当查询的条件范围内匹配到了我们的记录,就会触发。通过临建锁可以解决幻读的问题。

每个数据行上的非唯一索引列上都会存在一把临键锁,当某个事务持有该数据行的临键锁时,会锁住一段左开右闭区间的数据。InnoDB 中行级锁是基于索引实现的,临键锁只与非唯一索引列有关,在唯一索引列(包括主键列)上不存在临键锁


与间隙锁类似,临键锁也是锁定一个区间。区别在于,触发临键锁时锁定的区间为左开右闭,即( ,值1 ]、( 值1, ]…

浅谈InnoDB存储引擎下锁的分类_意向锁_10


上图的sql转换为图片,可以理解为

根据sql可得我们锁定的范围为(3, 6] + (6, 8] ,即当我们插入的数据id = 8时,也会发生操作上的阻塞

浅谈InnoDB存储引擎下锁的分类_意向锁_11



二、表级别

再说一遍:InnoDB中行级锁是基于索引实现的,即需要命中索引,才能生效。当我们使用sql语句做查询操作,命中索引则添加行锁,此时,共享指定行;没有命中索引则添加表锁,则共享整张表



1、意向共享锁/IS锁(Intention Shared Locks)

表示事务准备给数据行添加共享锁(行级别)之前,会先给表添加一个意向共享锁(表级别)。该意向锁也是用来表示该表内部存在共享锁,为后期其他操作节省资源。



2、意向排他锁/IX锁(Intention Exclusive Locks)

表示事务准备给数据行添加排他锁(行级别)之前,会先给表添加一个意向排他锁(表级别)。该意向排他锁表示,该表内部存在排他锁,为后期其他操作节省资源。

由于意向锁的目的只是一个标识的作用,所以它们之间是相互不冲突的,即无论你添加了意向共享锁还是意向排他锁,我都可以再加其他的意向锁。

(2)意向共享锁/IS

(2)意向排他锁/IX

(1)意向共享锁/IS

兼容

兼容

(1)意向排他锁/IX

兼容

兼容


总结:

意向的共享锁和排他锁,与共享锁和排他锁之间的关系。就可以把意向锁抽象为行级锁,即去掉共享,那么就可以得到这样的关系。

至此,这四者的关系就全部梳理完毕,空缺的部分也在前文有演示。

共享锁

排他锁

意向共享锁

意向排他锁

共享锁

兼容

冲突

排他锁

冲突

冲突

意向共享锁

兼容

冲突

意向排他锁

冲突

冲突


3、意向锁作用

意向锁是为了加表锁时提高互斥判断的效率,加速检测表锁与行锁的冲突。

共享锁S和排他锁X都是行锁,事务在对某一行加锁时需要先找到该行,然后判断和已有锁是否兼容,寻找行的过程比较费时,因此InnoDB采用意向锁的形式将粒度粗化为表级锁,这样在申请上锁时,先用表级意项锁和已有锁比较是否兼容,如果不兼容直接阻塞,否则才去判断行级锁和已有锁的兼容性。

大白话理解,事务A对数据行添加了共享锁。事务B要新添加一个共享锁,由于共享锁直接不会发生冲突,所以肯定会加锁成功。但如果事务B是要对数据表添加一个排他锁,就需要去数据表内部进行判断,直到遍历到事务A的共享锁,然后加锁失败;如果现在有了意向锁,事务B就能在遍历表数据之前,与表上的意向锁状态进行比较,由于共享锁和意向排他锁是冲突的,所以就能直接返回加锁失败。

事务A对数据行添加了排他锁。在没有意向锁排他锁时,事务B要新添加一个数据表的排他锁时,就需要去数据表内部进行遍历判断,直到遍历到事务A的排他锁,然后加锁失败;如果有了意向锁,那么事务B就可以直接跟事务A操作时,表上的意向锁状态进行比较,由于排他锁和意向排他锁冲突,所以就能直接返回加锁失败。



三、补充锁



1、插入意向锁(Insert Intention Locks)

Insert Intention Locks译称插入意向锁,首先强调插入意向锁是间隙锁的一种。不是意向锁,在insert操作时产生。

此段解释来自官网

Gap Lock中存在一种插入意向锁(Insert Intention Lock),在insert操作时产生。在多事务同时写入不同数据至同一索引间隙的时候,并不需要等待其他事务完成,不会发生锁等待。

假设有一个记录索引包含键值4和7,不同的事务分别插入5和6,每个事务都会产生一个加在4-7之间的插入意向锁,获取在插入行上的排它锁,但是不会被互相锁住,因为数据行并不冲突。

即可以理解为,插入意向锁是为了提高插入性能。多个事务同时操作一个表,在同一个索引,同一个范围区间插入记录时,如果插入的位置不冲突,不会阻塞彼此。如果没有这个锁,我们每次添加数据时就需要去申请排他锁,影响效率。


2、自增锁(AUTO-INC Locks)

自增锁是MySQL一种特殊的锁,如果表中存在自增字段,MySQL便会自动维护一个自增锁。

自增锁是一种特殊的表级锁,由插入带有自动递增列的表中的事务获取。在最简单的情况下,如果一个事务正在向表中插入值,则任何其他事务都必须等待自己向该表中进行插入,以便第一个事务插入的行接收连续的主键值。

即如果事务A正在往表中插入记录,所有事务B的插入必须等待,以便事务A插入的行,是连续的主键值。与此同时,InnoDB提供了innodb_autoinc_lock_mode配置,可以调节与改变该锁的模式与行为


3、空间锁(Predicate Locks for Spatial Indexes)

未懂,待补充。

空间索引的Predicate Locks,在RR级别或者 SERIALIZABLE 下,Next-key 锁无法有效保证空间索引的正常,所以产生此锁