一、InnoDB中的行锁
行锁(Row Lock)也称为记录锁,也就是锁住某一行(某条记录 row)。需要注意的是,MySQL 服务器层并没有实现
行锁机制
,行级锁只在存储引擎层实现
。
-
行锁优点
:锁定力度小,发生锁冲突
概率低,可以实现的并发度高
-
行锁缺点
:对于锁的开销比较大
,加锁会比较慢,容易出现死锁
情况 -
InnoDB 与 MyISAM 的最大不同
- 1、InnoDB支持事务
- 2、InnoDB采用行级锁
二、准备数据
1、创建数据库
CREATE TABLE `student`
(
`id` INT NOT NULL,
`name` VARCHAR(20) DEFAULT NULL,
`class` VARCHAR(20) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE = InnoDB;
2、创建数据
INSERT INTO student
VALUES (1, '张三', '一班'),
(3, '李四', '一班'),
(8, '王五', '二班'),
(15, '赵六', '二班'),
(20, '钱七', '三班');

三、 InnoDB行锁-记录锁(Record Locks)
记录锁
也就是仅仅把一条记录
锁上,官方的类型名称为:LOCK_REC_NOT_GAP
。比如把id值为8的那条记录加一个记录锁的示意图如图所示。仅仅是锁住了id值为8的记录,对周围的数据没有影响。

1、实战
步骤1、Session1,开启手动提交事务,更新id为1的数据
BEGIN ;
UPDATE student
SET name = '张三 1'
WHERE id = 1;
步骤2、Session1,查询数据
SELECT *
FROM student;

步骤3、Session2,开启手动提交事务,查询列表数据
BEGIN ;
SELECT *
FROM student;

步骤4、Session2,修改id为3的数据
UPDATE student
SET name = '李四 1'
WHERE id = 3;

步骤5、Session2,修改id为1的数据
UPDATE student
SET name = '张三 2'
WHERE id = 1;


步骤6、Session1和Session2,提交事务
COMMIT ;


小结:记录锁是有S锁
和X锁
之分的,称之为 S型记录锁
和 X型记录锁
- 当一个事务获取了一条记录的
S型记录锁
后,其他事务也可以继续获取该记录的S型记录锁
,但不可以继续获取X型记录锁
- 当一个事务获取了一条记录的
X型记录锁
后,其他事务既不可以继续获取该记录的S型记录锁
,也不可以继续获取X型记录锁
四、 间隙锁(Gap Locks)
MySQL
在REPEATABLE READ
隔离级别下是可以解决幻读
问题的,解决方案有两种,方案一:可以使用 MVCC 方案解决
;方案二:可以采用 加锁 方案解决
。但是方案二有个大问题,就是事务在第一次
执行读取操作时,那些幻影记录
尚不存在,我们无法给这些幻影记录
加上 记录锁 。InnoDB提出了一种称之为Gap Locks
的锁,官方的类型名称为:LOCK_GAP
,我们可以简称为 gap锁 。

-
图中id值为8的记录加了
gap锁
,意味着不允许别的事务在id值为8的记录前边的间隙插入新记录
,其实就是id列的值(3, 8)这个区间的新记录是不允许立即插入的。比如,有另外一个事务
想插入一条id值为4的新记录,它定位到该条新记录的下一条记录的id值为8,而这条记录上又有一个gap锁,所以就会阻塞插入操作,直到拥有这个gap锁的事务提交了之后,id列的值在区间(3, 8)中的新记录才可以被插入。 -
gap锁的提出仅仅是为了防止插入 幻影记录 而提出的
。虽然有共享 gap 锁
和独占gap 锁
这样的说法,但是它们起到的作用是相同的。而且如果对一条记录加了gap锁(不论是共享 gap 锁还是独立 gap锁)
,并不会限制其他事务对这条记录记录锁
或者继续加gap锁
1、实战
步骤1:Session1为不存在记录添加共享锁
BEGIN ;
SELECT *
FROM student
WHERE id = 5 LOCK IN SHARE MODE ;
步骤2:Session2为不存在记录添加排他锁
SELECT *
FROM student
WHERE id = 5 FOR UPDATE ;
-
Session2并不会被堵住。因为表里并没有
id=5
这个记录,因此 Session1 加的是间隙锁(3, 8)
。而Session2 也是在这个间隙
上加的间隙锁
。它们有共同的目标,即:保护这个间隙,不允许插入值。但是它们之间是不冲突的。 -
给一条记录加了
gap锁
只是不允许
其他事务往这条记录前边的间隙插入新记录
,那对于最后一条记录之后的间隙,也就是 student 表中id值为20的记录之后的间隙该咋办?也就是说给哪条记录加gap锁
才能阻止其他事务插入id值在(20, Supremum)这个区间的新记录?-
Infimum记录
,表示该页面中最小的记录 -
Supremum记录
,表示该页面中最大的记录
-
-
为了实现阻止其他事务插入id值在(20, Supremum)这个区间的新记录,可以给索引中的最后一条记录,也就是 id 值为 20 的那条记录所在页面的
Supremum
记录加上一个 gap 锁image.png
-
SQL
SELECT *
FROM student
WHERE id > 20 FOR SHARE ;
- 查看情况
SELECT *
FROM performance_schema.data_locks;

2、实战:插入同间隙数据会阻塞
步骤1:Session1 修改为手动提交事务
COMMIT ;
步骤2:Session1 产看锁情况
SELECT *
FROM performance_schema.data_locks;
步骤3:Session1 为间隙添加排他锁
SELECT *
FROM student
WHERE id = 5 FOR UPDATE ;
步骤4:Session1 产看锁情况
SELECT *
FROM performance_schema.data_locks;

步骤5:Session2 修改为手动提交事务
COMMIT ;
步骤6:Session2 在间隙中插入数据
INSERT INTO student
VALUE (4, 'Raven', '二班');

步骤7:Session1 产看锁情况
SELECT *
FROM performance_schema.data_locks;

步骤8:可以插入非锁定间隙
数据,锁定区间为(3, 8)
INSERT INTO student
VALUE (16, 'Raven', '三班');
3、实战:插入同一间隙
数据会阻塞
步骤1:Session1 开启手动提交事务,并且加间隙锁
BEGIN ;
SELECT *
FROM student
WHERE id = 4 FOR UPDATE ;
步骤2:Session2 开启手动提交事务,并且在同一间隙上加间隙锁
BEGIN ;
SELECT *
FROM student
WHERE id = 5 FOR UPDATE ;
步骤3:Session2 在间隙中添加数据
INSERT INTO student
VALUE (5, 'Raven-5', '三班');
步骤4:Session1 在间隙中添加数据
INSERT INTO student
VALUE (4, 'Raven-4', '三班');

小结
- Session1 执行
SELECT . . . FOR UPDATE
语句,由于id=5
这一行并不存在,因此会加上间隙锁(3, 8)
- Session2 执行
SELECT . . . FOR UPDATE
语句,同样会加上间隙锁(3, 8)
,间隙锁之间不会冲突,因此这个语句可以执行成功 - Session2 试图插入一行数据被 Session1 的
间隙锁
阻塞,进入等待 - Session1 试图插入一行数据被 Session2 的
间隙锁
阻塞,进入等待 - 至此,两个 Session 进入互相等待状态,形成死锁。当然,InnoDB 的死锁检测马上就发现了这对
死锁关系
,让 Session1 的 INSERT 语句报错返回
五、 临键锁(Next-Key Locks)
有时候既想
锁住某条记录
,又想阻止
其他事务在该记录前边的间隙插入新记录
,所以InnoDB就提出了一种称之为Next-Key Locks
的锁,官方的类型名称为:LOCK_ORDINARY
,可以简称为next-key锁
。Next-Key Locks
是在存储引擎 innodb
、事务级别在可重复读
的情况下使用的数据库锁
,innodb默认的锁就是Next-Key locks。

-
next-key锁
的本质就是一个记录锁
和一个gap锁
的合体,它既能保护该条记录,又能阻止别的事务将新纪录插入被保护记录前边的间隙
-
SQL
SELECT *
FROM student
WHERE id <= 8 AND id > 3 FOR UPDATE ;
六、插入意向锁(Insert Intention Locks)
一个事务在
插入
一条记录时需要判断一下插入位置是不是被别的事务加了gap锁 ( next-key锁 也包含 gap锁 )
,如果有的话,插入操作需要等待,直到拥有gap锁
的那个事务提交。但是InnoDB规定事务在等待的时候也需要在内存中生成一个锁结构
,表明有事务想在某个间隙
中插入
新记录,但是现在在等待。InnoDB就把这种类型的锁命名为Insert Intention Locks
,官方的类型名称为:LOCK_INSERT_INTENTION
,我们称为插入意向锁
。插入意向锁是一种Gap锁
,不是意向锁
,在insert操作时产生。
插入意向锁
是在插入一条记录行前,由INSERT 操作产生的一种间隙锁
。该锁用以表示插入意向,当多个事务在同一区间(gap)插入位置不同的多条数据时,事务之间不需要互相等待。假设存在两条值分别为 4 和 7 的记录,两个不同的事务分别试图插入值为 5 和 6 的两条记录,每个事务在获取插入行上独占的(排他)锁前,都会获取 (4, 7) 之间的间隙锁,但是因为数据行之间并不冲突
,所以两个事务之间并不会产生冲突(阻塞等待)
-
插入意向锁
是在插入一条记录行前,由INSERT 操作产生的一种间隙锁
。 插入意向锁并不会阻止别的事务继续获取该记录上任何类型的锁
-
插入意向锁
是一种特殊的间隙锁
——间隙锁可以锁定开区间内的部分记录 -
插入意向锁之间互不排斥
,所以即使多个事务在同一区间插入多条记录,只要记录本身(主键、唯一索引)不冲突,那么事务之间就不会出现冲突等待
-
虽然插入意向锁
中含有意向锁
三个字,但是它并不属于意向锁
而属于间隙锁
,因为意向锁是表锁
而插入意向锁是 行锁

-
现在 T1 为id值为 8 的记录加了一个
gap锁
,然后 T2 和 T3 分别想向student
表中插入 id 值分别为 4、5的两条记录,所以现在为 id 值为 8 的记录加的锁示意图image.png
-
由于 T1 持有
gap 锁
,所以 T2和T3需要生成一个插入意向锁
的锁结构并且处于等待状态
。当 T1 提交后会把它获取到的锁都释放掉
,这样T2和T3就能获取到对应的插入意向锁了(本质上就是把插入意向锁对应锁结构的is_waiting
属性改为 false),T2和T3之间也并不会相互阻塞,它们可以同时获取到id值为8的插入意向锁,然后执行插入操作。事实上插入意向锁并不会阻止别的事务继续获取该记录上任何类型的锁
网友评论