InnoDB存储引擎中的行锁机制深入分析

前言

当前的mysql或mariadb数据库默认存储引擎都是InnoDB。相较于MyISAM引擎,InnoDB引擎的一大特点就是支持行级锁。相比于表锁,行锁降低了锁的颗粒度,从而提高了数据库并发能力。与此同时,锁的颗粒度的降低也带来了更多样的加锁逻辑,也带来了因行锁导致的死锁问题。本文通过理论结合实际案例,来说明InnoDB引擎中行锁的加锁逻辑。

行级锁分类

记录锁:Record Lock,仅对记录本身上锁。

间隙锁:间隙锁,锁定一个范围,但是不包含记录本身。

临键锁:Next-Key Lock,即是记录锁+间隙锁,锁定一个范围,并且锁定记录本身。

关于行锁的相关逻辑

1.以上行级锁,锁的对象都是索引,包括聚簇索引和二级索引。

2.加锁的基本单位都是临键锁。

3.临键锁锁住的范围是一个前开后闭区间。

4.临键锁在一定条件下会退化成记录锁或间隙锁。

5.间隙锁之间是兼容的,无论锁是排他锁还是共享锁。

6.插入意向锁和间隙锁是不兼容的。

实例说明

唯一索引

*示例数据语句*

CREATE TABLE test_lock (

id int(20) unsigned NOT NULL AUTO_INCREMENT COMMENT ‘主键’,

name varchar(32) NOT NULL ,

​ PRIMARY KEY (id)

);

insert into test_lock values (1,“a”),(10,“b”),(20,“c”),(30,“d”),(40,“e”);

唯一索引等值查询
查询值存在

*此时,查到存在的记录之后,临键锁将会退化成一个记录锁,仅锁住该条记录,而不锁左右间隙。*

*案例1:*

在这里插入图片描述

本例中,临键锁最终退化为仅仅锁住id=10的记录锁。

在mysql中可以使用select * from performance_schema.data_locks\G;命令来查看,锁详情信息。

如下:

mysql> select * from performance_schema.data_locks\G;

*************************** 1. row ***************************

​ ENGINE: INNODB

​ ENGINE_LOCK_ID: 140107077987544:1067:140107000654256

ENGINE_TRANSACTION_ID: 1823

​ THREAD_ID: 46

​ EVENT_ID: 26

​ OBJECT_SCHEMA: test

​ OBJECT_NAME: test_lock

​ PARTITION_NAME: NULL

SUBPARTITION_NAME: NULL

​ INDEX_NAME: NULL

OBJECT_INSTANCE_BEGIN: 140107000654256

*LOCK_TYPE: TABLE*

*LOCK_MODE: IX*

​ LOCK_STATUS: GRANTED

​ LOCK_DATA: NULL

*************************** 2. row ***************************

​ ENGINE: INNODB

​ ENGINE_LOCK_ID: 140107077987544:2:4:3:140107000651264

ENGINE_TRANSACTION_ID: 1823

​ THREAD_ID: 46

​ EVENT_ID: 26

​ OBJECT_SCHEMA: test

​ OBJECT_NAME: test_lock

​ PARTITION_NAME: NULL

SUBPARTITION_NAME: NULL

*INDEX_NAME: PRIMARY*

OBJECT_INSTANCE_BEGIN: 140107000651264

*LOCK_TYPE: RECORD*

*LOCK_MODE: X,REC_NOT_GAP*

​ LOCK_STATUS: GRANTED

*LOCK_DATA: 10*

可以看到有两条锁信息:

第一条中LOCK_MODE: IX表示为意向排他锁,LOCK_TYPE: TABLE表示为该锁是表级别的。

第二条中LOCK_MODE: X,REC_NOT_GAP中X表示为排他锁,REC_NOT_GAP表示为记录锁(不包含间隙),LOCK_TYPE: RECORD表示为行锁。INDEX_NAME: PRIMARY表示该锁的对象是主键。LOCK_DATA: 10,表示锁范围最右侧是id=10的记录。

查询值不存在

此时查不到等值记录,临键锁将会退化成一个间隙锁,锁住不存在记录左右两旁的间隙,直到遇到左边和右边记录值。

*案例2:*

在这里插入图片描述

本例中,临键锁退化成一个(10,20)的间隙锁。

锁详细信息如下:

mysql> select * from performance_schema.data_locks\G;

*************************** 1. row ***************************

​ ENGINE: INNODB

​ ENGINE_LOCK_ID: 140107077987544:1067:140107000654256

ENGINE_TRANSACTION_ID: 1834

​ THREAD_ID: 46

​ EVENT_ID: 32

​ OBJECT_SCHEMA: test

​ OBJECT_NAME: test_lock

​ PARTITION_NAME: NULL

SUBPARTITION_NAME: NULL

​ INDEX_NAME: NULL

OBJECT_INSTANCE_BEGIN: 140107000654256

​ LOCK_TYPE: TABLE

​ LOCK_MODE: IX

​ LOCK_STATUS: GRANTED

​ LOCK_DATA: NULL

*************************** 2. row ***************************

​ ENGINE: INNODB

​ ENGINE_LOCK_ID: 140107077987544:2:4:4:140107000651264

ENGINE_TRANSACTION_ID: 1834

​ THREAD_ID: 46

​ EVENT_ID: 32

​ OBJECT_SCHEMA: test

​ OBJECT_NAME: test_lock

​ PARTITION_NAME: NULL

SUBPARTITION_NAME: NULL

*INDEX_NAME: PRIMARY*

OBJECT_INSTANCE_BEGIN: 140107000651264

*LOCK_TYPE: RECORD*

*LOCK_MODE: X,GAP*

​ LOCK_STATUS: GRANTED

​ LOCK_DATA: 20

2 rows in set (0.00 sec)

跟前一个案例的区别在于第二个锁信息的LOCK_MODE: X,GAP,其中的GAP表明是一个间隙锁。

唯一索引范围查询
大于某值的范围查询

场景为唯一索引的大于范围查询时,会给所有满足条件的记录加上临键锁,包含一个指向无穷大的临键锁。

*案例3:*

在这里插入图片描述

*锁详细信息如下:*

mysql> select * from performance_schema.data_locks\G;

*************************** 1. row ***************************

​ ENGINE: INNODB

​ ENGINE_LOCK_ID: 140107077987544:1069:140107000654256

ENGINE_TRANSACTION_ID: 1893

​ THREAD_ID: 46

​ EVENT_ID: 58

​ OBJECT_SCHEMA: test

​ OBJECT_NAME: test_lock

​ PARTITION_NAME: NULL

SUBPARTITION_NAME: NULL

​ INDEX_NAME: NULL

OBJECT_INSTANCE_BEGIN: 140107000654256

​ LOCK_TYPE: TABLE

​ LOCK_MODE: IX

​ LOCK_STATUS: GRANTED

​ LOCK_DATA: NULL

*************************** 2. row ***************************

​ ENGINE: INNODB

​ ENGINE_LOCK_ID: 140107077987544:4:4:1:140107000651264

ENGINE_TRANSACTION_ID: 1893

​ THREAD_ID: 46

​ EVENT_ID: 58

​ OBJECT_SCHEMA: test

​ OBJECT_NAME: test_lock

​ PARTITION_NAME: NULL

SUBPARTITION_NAME: NULL

​ INDEX_NAME: PRIMARY

OBJECT_INSTANCE_BEGIN: 140107000651264

​ LOCK_TYPE: RECORD

*LOCK_MODE: X*

​ LOCK_STATUS: GRANTED

*LOCK_DATA: supremum pseudo-record*

*************************** 3. row ***************************

​ ENGINE: INNODB

​ ENGINE_LOCK_ID: 140107077987544:4:4:6:140107000651264

ENGINE_TRANSACTION_ID: 1893

​ THREAD_ID: 46

​ EVENT_ID: 58

​ OBJECT_SCHEMA: test

​ OBJECT_NAME: test_lock

​ PARTITION_NAME: NULL

SUBPARTITION_NAME: NULL

​ INDEX_NAME: PRIMARY

OBJECT_INSTANCE_BEGIN: 140107000651264

​ LOCK_TYPE: RECORD

*LOCK_MODE: X*

​ LOCK_STATUS: GRANTED

*LOCK_DATA: 40*

可以看到存在两个临键锁(LOCK_MODE: X)。一个是(30,40],一个是(40,无穷大(supremum pseudo-record))。所以锁住的区间是(30,无穷大]。

小于某值的范围查询

唯一索引的小于范围,查询会给所有符合条件的记录加临键锁,并在向右侧查询的过程,遇到第一个不符合条件的记录时,临键锁将退化为间隙锁。

*案例4:*

在这里插入图片描述

*锁详细信息如下:*

mysql> select * from performance_schema.data_locks\G;

*************************** 1. row ***************************

​ ENGINE: INNODB

​ ENGINE_LOCK_ID: 140107077987544:1069:140107000654256

ENGINE_TRANSACTION_ID: 1896

​ THREAD_ID: 46

​ EVENT_ID: 64

​ OBJECT_SCHEMA: test

​ OBJECT_NAME: test_lock

​ PARTITION_NAME: NULL

SUBPARTITION_NAME: NULL

​ INDEX_NAME: NULL

OBJECT_INSTANCE_BEGIN: 140107000654256

​ LOCK_TYPE: TABLE

​ LOCK_MODE: IX

​ LOCK_STATUS: GRANTED

​ LOCK_DATA: NULL

*************************** 2. row ***************************

​ ENGINE: INNODB

​ ENGINE_LOCK_ID: 140107077987544:4:4:2:140107000651264

ENGINE_TRANSACTION_ID: 1896

​ THREAD_ID: 46

​ EVENT_ID: 64

​ OBJECT_SCHEMA: test

​ OBJECT_NAME: test_lock

​ PARTITION_NAME: NULL

SUBPARTITION_NAME: NULL

​ INDEX_NAME: PRIMARY

OBJECT_INSTANCE_BEGIN: 140107000651264

​ LOCK_TYPE: RECORD

*LOCK_MODE: X*

​ LOCK_STATUS: GRANTED

​ LOCK_DATA: 1

*************************** 3. row ***************************

​ ENGINE: INNODB

​ ENGINE_LOCK_ID: 140107077987544:4:4:3:140107000651264

ENGINE_TRANSACTION_ID: 1896

​ THREAD_ID: 46

​ EVENT_ID: 64

​ OBJECT_SCHEMA: test

​ OBJECT_NAME: test_lock

​ PARTITION_NAME: NULL

SUBPARTITION_NAME: NULL

​ INDEX_NAME: PRIMARY

OBJECT_INSTANCE_BEGIN: 140107000651264

​ LOCK_TYPE: RECORD

*LOCK_MODE: X*

​ LOCK_STATUS: GRANTED

​ LOCK_DATA: 10

*************************** 4. row ***************************

​ ENGINE: INNODB

​ ENGINE_LOCK_ID: 140107077987544:4:4:4:140107000651608

ENGINE_TRANSACTION_ID: 1896

​ THREAD_ID: 46

​ EVENT_ID: 64

​ OBJECT_SCHEMA: test

​ OBJECT_NAME: test_lock

​ PARTITION_NAME: NULL

SUBPARTITION_NAME: NULL

​ INDEX_NAME: PRIMARY

OBJECT_INSTANCE_BEGIN: 140107000651608

​ LOCK_TYPE: RECORD

*LOCK_MODE: X,GAP*

​ LOCK_STATUS: GRANTED

​ LOCK_DATA: 20

可以看到有两个临键锁(无穷小,1],(1,10]和一个间隙锁(10,20)。

所以锁住的范围为(无穷小,20)。

非唯一索引

案例数据准备:

首先给上述演示表test_lock的增加code字段,并加非唯一的索引。

建表语句改为:

CREATE TABLE test_lock (

id int unsigned NOT NULL AUTO_INCREMENT,

name varchar(32) NOT NULL,

code int unsigned NOT NULL,

PRIMARY KEY (id),

KEY index_code (code)

);

语句插入改为:

insert into test_lock values (1,“a”,1),(10,“b”,10),(20,“c”,20),(30,“d”,30),(40,“e”,40);

非唯一索引等值查询
查询的记录存在时

二级索引等值查询时,由于记录存在,首先会给记录的二级索引加临键锁,然后给该记录对应的主键索引加记录锁,继续向右扫描,直到遇到下一个记录,并将该区间的临键锁退化成间隙锁。

*案例5:*
在这里插入图片描述

事务1的查询语句,会加上(10,20] Index_code二级索引的间隙锁,还有code=20记录主键索引的记录锁,以及Index_code二级索引的间隙锁(20,30]退化后的间隙锁(20,30)。

*锁详细信息如下:*

mysql> select * from performance_schema.data_locks\G;

*************************** 1. row ***************************

​ ENGINE: INNODB

​ ENGINE_LOCK_ID: 140107077987544:1072:140107000654256

ENGINE_TRANSACTION_ID: 2001

​ THREAD_ID: 46

​ EVENT_ID: 110

​ OBJECT_SCHEMA: test

​ OBJECT_NAME: test_lock

​ PARTITION_NAME: NULL

SUBPARTITION_NAME: NULL

​ INDEX_NAME: NULL

OBJECT_INSTANCE_BEGIN: 140107000654256

​ LOCK_TYPE: TABLE

​ LOCK_MODE: IX

​ LOCK_STATUS: GRANTED

​ LOCK_DATA: NULL

*************************** 2. row ***************************

​ ENGINE: INNODB

​ ENGINE_LOCK_ID: 140107077987544:7:5:4:140107000651264

ENGINE_TRANSACTION_ID: 2001

​ THREAD_ID: 46

​ EVENT_ID: 110

​ OBJECT_SCHEMA: test

​ OBJECT_NAME: test_lock

​ PARTITION_NAME: NULL

SUBPARTITION_NAME: NULL

​ INDEX_NAME: index_code

OBJECT_INSTANCE_BEGIN: 140107000651264

​ LOCK_TYPE: RECORD

​ LOCK_MODE: X

​ LOCK_STATUS: GRANTED

​ LOCK_DATA: 20, 20

*************************** 3. row ***************************

​ ENGINE: INNODB

​ ENGINE_LOCK_ID: 140107077987544:7:4:4:140107000651608

ENGINE_TRANSACTION_ID: 2001

​ THREAD_ID: 46

​ EVENT_ID: 110

​ OBJECT_SCHEMA: test

​ OBJECT_NAME: test_lock

​ PARTITION_NAME: NULL

SUBPARTITION_NAME: NULL

*INDEX_NAME: PRIMARY*

OBJECT_INSTANCE_BEGIN: 140107000651608

​ LOCK_TYPE: RECORD

*LOCK_MODE: X,REC_NOT_GAP*

​ LOCK_STATUS: GRANTED

​ LOCK_DATA: 20

*************************** 4. row ***************************

​ ENGINE: INNODB

​ ENGINE_LOCK_ID: 140107077987544:7:5:5:140107000651952

ENGINE_TRANSACTION_ID: 2001

​ THREAD_ID: 46

​ EVENT_ID: 110

​ OBJECT_SCHEMA: test

​ OBJECT_NAME: test_lock

​ PARTITION_NAME: NULL

SUBPARTITION_NAME: NULL

​ INDEX_NAME: index_code

OBJECT_INSTANCE_BEGIN: 140107000651952

​ LOCK_TYPE: RECORD

*LOCK_MODE: X,GAP*

​ LOCK_STATUS: GRANTED

​ LOCK_DATA: 30, 30

可以看到,正如我们前文分析,给二级索引index_code上了临键锁和间隙锁,还给主键索引上了记录锁。LOCK_DATA: 20, 20 中第一个20表示二级索引记录,第二个20表示主键索引记录。

二级索引加锁范围的精细分析

在上述案例5中,需要重点注意的是事务4和事务5,在同样是code=10的情况下,事务4阻塞住,同时事务5可以顺利执行。

*为什么会出现这么奇怪的现象呢?*

这是由于二级索引中,数据的排列顺序是,先按二级索引字段排序,同样的二级索引数据,再按主键索引排序。所以上述查询语句会锁id=10,code=10 右侧的范围,及code=10,id>10的范围,而不会锁住code=10,id<10的范围。

如下图

在这里插入图片描述

查询的记录不存在时

这种情况,会给该二级索引左右数据范围内,加临键锁,并在向右查找遇到索引数据时退化为间隙锁。

*案例6:*
在这里插入图片描述

此查询语句会加一个(20,30)的间隙锁,由于没有符合条件的记录存在,无需再给主键索引加记录锁。

*锁详细信息如下:*

mysql> select * from performance_schema.data_locks\G;

*************************** 1. row ***************************

​ ENGINE: INNODB

​ ENGINE_LOCK_ID: 140107077987544:1072:140107000654256

ENGINE_TRANSACTION_ID: 2009

​ THREAD_ID: 46

​ EVENT_ID: 116

​ OBJECT_SCHEMA: test

​ OBJECT_NAME: test_lock

​ PARTITION_NAME: NULL

SUBPARTITION_NAME: NULL

​ INDEX_NAME: NULL

OBJECT_INSTANCE_BEGIN: 140107000654256

​ LOCK_TYPE: TABLE

​ LOCK_MODE: IX

​ LOCK_STATUS: GRANTED

​ LOCK_DATA: NULL

*************************** 2. row ***************************

​ ENGINE: INNODB

​ ENGINE_LOCK_ID: 140107077987544:7:5:5:140107000651264

ENGINE_TRANSACTION_ID: 2009

​ THREAD_ID: 46

​ EVENT_ID: 116

​ OBJECT_SCHEMA: test

​ OBJECT_NAME: test_lock

​ PARTITION_NAME: NULL

SUBPARTITION_NAME: NULL

​ INDEX_NAME: index_code

OBJECT_INSTANCE_BEGIN: 140107000651264

​ LOCK_TYPE: RECORD

*LOCK_MODE: X,GAP*

​ LOCK_STATUS: GRANTED

​ LOCK_DATA: 30, 30

可以看到,只有一个意向排他锁和一个间隙锁。

非唯一索引的范围查询

非唯一索引的范围查询与之前加锁过程的区别是,该场景下临键锁不会再退化成记录锁或间隙锁。

*案例7:*
在这里插入图片描述

此查询语句,会在(30,40],(40,无穷大]加临键锁,并给查到的记录code=40对应的主键索引加记录锁。

*锁详细信息如下:*

mysql> select * from performance_schema.data_locks\G;

*************************** 1. row ***************************

​ ENGINE: INNODB

​ ENGINE_LOCK_ID: 140107077987544:1072:140107000654256

ENGINE_TRANSACTION_ID: 2016

​ THREAD_ID: 46

​ EVENT_ID: 124

​ OBJECT_SCHEMA: test

​ OBJECT_NAME: test_lock

​ PARTITION_NAME: NULL

SUBPARTITION_NAME: NULL

​ INDEX_NAME: NULL

OBJECT_INSTANCE_BEGIN: 140107000654256

​ LOCK_TYPE: TABLE

​ LOCK_MODE: IX

​ LOCK_STATUS: GRANTED

​ LOCK_DATA: NULL

*************************** 2. row ***************************

​ ENGINE: INNODB

​ ENGINE_LOCK_ID: 140107077987544:7:5:1:140107000651264

ENGINE_TRANSACTION_ID: 2016

​ THREAD_ID: 46

​ EVENT_ID: 124

​ OBJECT_SCHEMA: test

​ OBJECT_NAME: test_lock

​ PARTITION_NAME: NULL

SUBPARTITION_NAME: NULL

​ INDEX_NAME: index_code

OBJECT_INSTANCE_BEGIN: 140107000651264

​ LOCK_TYPE: RECORD

*LOCK_MODE: X*

​ LOCK_STATUS: GRANTED

​ LOCK_DATA: supremum pseudo-record

*************************** 3. row ***************************

​ ENGINE: INNODB

​ ENGINE_LOCK_ID: 140107077987544:7:5:6:140107000651264

ENGINE_TRANSACTION_ID: 2016

​ THREAD_ID: 46

​ EVENT_ID: 124

​ OBJECT_SCHEMA: test

​ OBJECT_NAME: test_lock

​ PARTITION_NAME: NULL

SUBPARTITION_NAME: NULL

​ INDEX_NAME: index_code

OBJECT_INSTANCE_BEGIN: 140107000651264

​ LOCK_TYPE: RECORD

*LOCK_MODE: X*

​ LOCK_STATUS: GRANTED

​ LOCK_DATA: 40, 40

*************************** 4. row ***************************

​ ENGINE: INNODB

​ ENGINE_LOCK_ID: 140107077987544:7:4:6:140107000651608

ENGINE_TRANSACTION_ID: 2016

​ THREAD_ID: 46

​ EVENT_ID: 124

​ OBJECT_SCHEMA: test

​ OBJECT_NAME: test_lock

​ PARTITION_NAME: NULL

SUBPARTITION_NAME: NULL

*INDEX_NAME: PRIMARY*

OBJECT_INSTANCE_BEGIN: 140107000651608

​ LOCK_TYPE: RECORD

*LOCK_MODE: X,REC_NOT_GAP*

​ LOCK_STATUS: GRANTED

​ LOCK_DATA: 40

可以看到二级索引的临键锁并未退化成其他锁,依然是LOCK_MODE: X。

无索引字段查询

无索引字段查询时,会给没有二级索引记录加临键锁,并给每个一级索引值加记录锁。这种情况等同于给全表加锁。

*案例8:*
在这里插入图片描述

锁详细信息太长,截取部分如下:

mysql> select * from performance_schema.data_locks\G;

*************************** 1. row ***************************

​ ENGINE: INNODB

​ ENGINE_LOCK_ID: 140107077987544:1072:140107000654256

ENGINE_TRANSACTION_ID: 2025

​ THREAD_ID: 46

​ EVENT_ID: 130

​ OBJECT_SCHEMA: test

​ OBJECT_NAME: test_lock

​ PARTITION_NAME: NULL

SUBPARTITION_NAME: NULL

​ INDEX_NAME: NULL

OBJECT_INSTANCE_BEGIN: 140107000654256

​ LOCK_TYPE: TABLE

​ LOCK_MODE: IX

​ LOCK_STATUS: GRANTED

​ LOCK_DATA: NULL

*************************** 2. row ***************************

​ ENGINE: INNODB

​ ENGINE_LOCK_ID: 140107077987544:7:4:1:140107000651264

ENGINE_TRANSACTION_ID: 2025

​ THREAD_ID: 46

​ EVENT_ID: 130

​ OBJECT_SCHEMA: test

​ OBJECT_NAME: test_lock

​ PARTITION_NAME: NULL

SUBPARTITION_NAME: NULL

​ INDEX_NAME: PRIMARY

OBJECT_INSTANCE_BEGIN: 140107000651264

​ LOCK_TYPE: RECORD

​ LOCK_MODE: X

​ LOCK_STATUS: GRANTED

​ LOCK_DATA: supremum pseudo-record

*************************** 3. row ***************************

​ ENGINE: INNODB

​ ENGINE_LOCK_ID: 140107077987544:7:4:2:140107000651264

ENGINE_TRANSACTION_ID: 2025

​ THREAD_ID: 46

​ EVENT_ID: 130

​ OBJECT_SCHEMA: test

​ OBJECT_NAME: test_lock

​ PARTITION_NAME: NULL

SUBPARTITION_NAME: NULL

​ INDEX_NAME: PRIMARY

OBJECT_INSTANCE_BEGIN: 140107000651264

​ LOCK_TYPE: RECORD

​ LOCK_MODE: X

​ LOCK_STATUS: GRANTED

​ LOCK_DATA: 1

*************************** 8. row ***************************

​ ENGINE: INNODB

​ ENGINE_LOCK_ID: 140107077987544:7:4:7:140107000651264

ENGINE_TRANSACTION_ID: 2025

​ THREAD_ID: 46

​ EVENT_ID: 130

​ OBJECT_SCHEMA: test

​ OBJECT_NAME: test_lock

​ PARTITION_NAME: NULL

SUBPARTITION_NAME: NULL

​ INDEX_NAME: PRIMARY

OBJECT_INSTANCE_BEGIN: 140107000651264

​ LOCK_TYPE: RECORD

​ LOCK_MODE: X

​ LOCK_STATUS: GRANTED

​ LOCK_DATA: 7

*************************** 9. row ***************************

​ ENGINE: INNODB

​ ENGINE_LOCK_ID: 140107077987544:7:4:9:140107000651264

ENGINE_TRANSACTION_ID: 2025

​ THREAD_ID: 46

​ EVENT_ID: 130

​ OBJECT_SCHEMA: test

​ OBJECT_NAME: test_lock

​ PARTITION_NAME: NULL

SUBPARTITION_NAME: NULL

​ INDEX_NAME: PRIMARY

OBJECT_INSTANCE_BEGIN: 140107000651264

​ LOCK_TYPE: RECORD

​ LOCK_MODE: X

​ LOCK_STATUS: GRANTED

​ LOCK_DATA: 31

通过上述案例,可以看到,所有其他需要获取锁的事务都被阻塞。通过锁信息可以看到,所有二级索引都被加了间隙锁,并且所有主键索引都被加了记录锁。

插入意向锁

除了上述的临键锁,间隙锁,记录锁之外,还存在一种特殊的间隙锁:插入意向锁。

插入意向锁虽然名字带意向锁,但和其他意向锁不同,它属于行锁级别。

插入意向锁只和间隙锁冲突,不同的插入意向锁之间可以共存。

插入意向锁可以理解为一个特殊的行锁,它只锁住记录本身,并未锁住左右间隙。这样就可以增加插入数据的并发能力,这也是插入意向锁产生的原因。

根据官网文档描述,在执行insert插入数据时,加锁流程如下:

\1. 在插入的索引间隙中,加插入意向锁。

\2. 如果存在唯一键冲突,给索引加S共享锁。

\3. 如果不存在冲突,给插入的记录加X排他锁。

通过上述的过程,可以分析出,插入数据时,如果存在间隙锁,则会导致阻塞。这种情况前面的案例中已经多次验证,不再赘述。

不同事务中,在同一个间隙中同时插入不同的记录,由于插入意向锁的并不锁间隙,所以可以同时插入唯一键不冲突的记录。

此时,二级索引间,并未产生常规的间隙锁,这与前面案例中的select …for update造成的当前读加锁语句,有所区别。

*案例9:*

在这里插入图片描述

*锁详细信息如下:*

mysql> select * from performance_schema.data_locks\G;

*************************** 1. row ***************************

​ ENGINE: INNODB

​ ENGINE_LOCK_ID: 140107077987544:1072:140107000654256

ENGINE_TRANSACTION_ID: 2045

​ THREAD_ID: 46

​ EVENT_ID: 150

​ OBJECT_SCHEMA: test

​ OBJECT_NAME: test_lock

​ PARTITION_NAME: NULL

SUBPARTITION_NAME: NULL

​ INDEX_NAME: NULL

OBJECT_INSTANCE_BEGIN: 140107000654256

*LOCK_TYPE: TABLE*

*LOCK_MODE: IX*

​ LOCK_STATUS: GRANTED

​ LOCK_DATA: NULL

此时事务2和事务3的插入语句都不阻塞,锁详情信息中,也只有事务1产生的一个意向排他锁。

总结

其他意向锁不同,它属于行锁级别。

插入意向锁只和间隙锁冲突,不同的插入意向锁之间可以共存。

插入意向锁可以理解为一个特殊的行锁,它只锁住记录本身,并未锁住左右间隙。这样就可以增加插入数据的并发能力,这也是插入意向锁产生的原因。

根据官网文档描述,在执行insert插入数据时,加锁流程如下:

\1. 在插入的索引间隙中,加插入意向锁。

\2. 如果存在唯一键冲突,给索引加S共享锁。

\3. 如果不存在冲突,给插入的记录加X排他锁。

通过上述的过程,可以分析出,插入数据时,如果存在间隙锁,则会导致阻塞。这种情况前面的案例中已经多次验证,不再赘述。

不同事务中,在同一个间隙中同时插入不同的记录,由于插入意向锁的并不锁间隙,所以可以同时插入唯一键不冲突的记录。

此时,二级索引间,并未产生常规的间隙锁,这与前面案例中的select …for update造成的当前读加锁语句,有所区别。

*案例9:*

[外链图片转存中…(img-iexvbKEh-1733723101629)]

*锁详细信息如下:*

mysql> select * from performance_schema.data_locks\G;

*************************** 1. row ***************************

​ ENGINE: INNODB

​ ENGINE_LOCK_ID: 140107077987544:1072:140107000654256

ENGINE_TRANSACTION_ID: 2045

​ THREAD_ID: 46

​ EVENT_ID: 150

​ OBJECT_SCHEMA: test

​ OBJECT_NAME: test_lock

​ PARTITION_NAME: NULL

SUBPARTITION_NAME: NULL

​ INDEX_NAME: NULL

OBJECT_INSTANCE_BEGIN: 140107000654256

*LOCK_TYPE: TABLE*

*LOCK_MODE: IX*

​ LOCK_STATUS: GRANTED

​ LOCK_DATA: NULL

此时事务2和事务3的插入语句都不阻塞,锁详情信息中,也只有事务1产生的一个意向排他锁。

总结

本文通过InnoDB存储引擎索引相关案例,展示了在不同类型索引和不同查询范围的场景下,InnoDB存储引擎行锁机制的不同表现。希望可以为同学们深入了解Innodb行锁机制,避免或解决相关的死锁问题带来帮助。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值