插入意向锁(Insert Intension Lock)
是实际在插入操作前,提出一个插入的意向,检查其所属的范围是否存在间隙锁。
注意,插入意向锁虽然叫做意向锁,但其本质上是一种间隙锁,因为意向锁是表锁,而插入意向锁是行锁。
MySql 手册 中对插入意向锁的解释:
插入意向锁是在插入一条记录行前,由 INSERT 操作产生的一种间隙锁。该锁用以表示插入意向,当多个事务在同一区间(gap)插入位置不同的多条数据时,事务之间不需要互相等待。假设存在两条值分别为 4 和 7 的记录,两个不同的事务分别试图插入值为 5 和 6 的两条记录,每个事务在获取插入行上独占的(排他)锁前,都会获取(4,7)之间的间隙锁,但是因为数据行之间并不冲突,所以两个事务之间并不会产生冲突(阻塞等待)。
插入意向锁的特点:
-
是一种特殊的间隙锁,用于锁定两个记录之间的间隙
-
插入意向锁之间互不排斥,可以共存
-
插入意向锁与间隙锁是互斥的,不能共存
那么问题来了:既然有了间隙锁,为什么还需要插入意向锁?
-
间隙锁是为了防止其他事务的插入,为了防止幻读而设计的;插入意向锁是为了优化并发插入效率而设计的
-
间隙锁之间可以共存,即:两个事务在同一时间可以拥有同一个间隙的间隙锁(因为间隙锁本质是为了防止插入)
-
插入意向锁和间隙锁不能共存,即:两个事务不能在同一时间内,一个拥有间隙锁,另一个拥有该间隙的插入意向锁
-
插入意向锁是配合着间隙锁使用的,二者相互配合才能实现可重复读
比如我们创建如下表结构:
CREATE TABLE IF NOT EXISTS `test1` (
`id` INT PRIMARY KEY,
`name` VARCHAR(100)
);
INSERT INTO test1 (id, name) VALUES (15, 'A'), (20, 'B'), (30, 'C');
id | name |
15 | A |
20 | B |
30 | C |
开启以下2个事务:
事务1 | 事务2 | |
Begin; | Begin; | |
Time1 | UPDATE test1 SET name = 'D' WHERE id = 25; (获得IX锁和20-30记录的间隙X锁) | |
Time2 | UPDATE test1 SET name = 'E' WHERE id = 26; (获得IX锁和20-30记录的间隙X锁) | |
Time3 | INSERT INTO test1 (id, name) VALUES (25, 'D'); (等待20-30记录的插入意向锁) | |
Time4 | INSERT INTO test1 (id, name) VALUES (26, 'E'); (等待20-30记录的插入意向锁) |
-
TIme1:事务1获得了意向排他锁和 (20, 30) 范围的间隙锁
-
Time2:事务2获得了意向排他锁和 (20, 30) 范围的间隙锁(意向锁和意向锁之间不会冲突,间隙锁和间隙锁之间也不会冲突)
-
TIme3:INSERT 操作尝试获得 (20, 30) 范围的插入意向锁,但被事务2的间隙锁阻塞
-
TIme4:INSERT 操作尝试获得 (20, 30) 范围的插入意向锁,但被事务1的间隙锁阻塞
因此循环等待造成了死锁。
可以查看 INNODB 死锁日志:
SHOW ENGINE INNODB STATUS;
输入日志内容:
...
------------------------
LATEST DETECTED DEADLOCK
------------------------
2024-11-21 15:24:06 0x16da9b000
*** (1) TRANSACTION:
TRANSACTION 2180072, ACTIVE 14 sec inserting
mysql tables in use 1, locked 1
LOCK WAIT 3 lock struct(s), heap size 1128, 2 row lock(s)
MySQL thread id 3296, OS thread handle 6156677120, query id 174792 localhost 127.0.0.1 root update
INSERT INTO test1 (id, name) VALUES (25, 'D')
*** (1) HOLDS THE LOCK(S):
RECORD LOCKS space id 508 page no 4 n bits 72 index PRIMARY of table `db-test`.`test1` trx id 2180072 lock_mode X locks gap before rec
Record lock, heap no 4 PHYSICAL RECORD: n_fields 4; compact format; info bits 0
0: len 4; hex 8000001e; asc ;;
1: len 6; hex 0000002143e2; asc !C ;;
2: len 7; hex 820000016d012a; asc m *;;
3: len 1; hex 43; asc C;;
*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 508 page no 4 n bits 72 index PRIMARY of table `db-test`.`test1` trx id 2180072 lock_mode X locks gap before rec insert intention waiting
Record lock, heap no 4 PHYSICAL RECORD: n_fields 4; compact format; info bits 0
0: len 4; hex 8000001e; asc ;;
1: len 6; hex 0000002143e2; asc !C ;;
2: len 7; hex 820000016d012a; asc m *;;
3: len 1; hex 43; asc C;;
*** (2) TRANSACTION:
TRANSACTION 2180073, ACTIVE 8 sec inserting
mysql tables in use 1, locked 1
LOCK WAIT 3 lock struct(s), heap size 1128, 2 row lock(s)
MySQL thread id 17733, OS thread handle 6154448896, query id 174796 localhost 127.0.0.1 root update
INSERT INTO test1 (id, name) VALUES (26, 'E')
*** (2) HOLDS THE LOCK(S):
RECORD LOCKS space id 508 page no 4 n bits 72 index PRIMARY of table `db-test`.`test1` trx id 2180073 lock_mode X locks gap before rec
Record lock, heap no 4 PHYSICAL RECORD: n_fields 4; compact format; info bits 0
0: len 4; hex 8000001e; asc ;;
1: len 6; hex 0000002143e2; asc !C ;;
2: len 7; hex 820000016d012a; asc m *;;
3: len 1; hex 43; asc C;;
*** (2) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 508 page no 4 n bits 72 index PRIMARY of table `db-test`.`test1` trx id 2180073 lock_mode X locks gap before rec insert intention waiting
Record lock, heap no 4 PHYSICAL RECORD: n_fields 4; compact format; info bits 0
0: len 4; hex 8000001e; asc ;;
1: len 6; hex 0000002143e2; asc !C ;;
2: len 7; hex 820000016d012a; asc m *;;
3: len 1; hex 43; asc C;;
*** WE ROLL BACK TRANSACTION (2)
...
参考:
MySQL :: MySQL 8.0 Reference Manual :: 17.7.1 InnoDB Locking
字节面试:加了什么锁,导致死锁的? | 小林coding