前言
当前的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行锁机制,避免或解决相关的死锁问题带来帮助。