死锁是指两个或多个事务在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,这些事务都将无法向前推进,这里主要介绍插入时发生死锁的一些情况
模拟插入时发生死锁
新建一张表
表结构
CREATE TABLE `test_test` (
`id` int unsigned NOT NULL AUTO_INCREMENT,
`version` smallint DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
1. 插入两条相同的数据
t1 | t2 | 状态 |
---|---|---|
begin; | begin; | |
insert ignore test_test (id, version) values (5, 1); | 成功 | |
insert ignore test_test (id, version) values (5, 1); | 锁等待状态 |
insert加的是隐式锁,隐式锁可以理解为没有锁
在t1插入记录时是不加锁的,这个时候事务t1还未提交的情况下,事务t2尝试插入的时候,发现有这条记录,t2尝试获取S锁,会判定记录上的事务id是否活跃,如果活跃的话,说明事务未结束,会帮t1把它的隐式锁提升为显式锁(X锁)
2. 批量插入顺序不一致的导致的死锁
t1 | t2 | 状态 |
---|---|---|
begin | begin | |
insert ignore test_test (id, version) values (5, 1); | 成功 | |
insert ignore test_test (id, version) values (6, 2); | 成功 | |
insert ignore test_test (id, version) values (6, 2); | t1尝试获取S锁,把t2的隐式锁提升为显示X锁,进入DB_LOCK_WAIT | |
insert ignore test_test (id, version) values (5, 1); | t2尝试获取S锁,把t1的隐式锁升级为显示X锁,产生死锁 |
3. 三个 insert 一个回滚造成的死锁
t1 | t2 | t3 | 说明 |
---|---|---|---|
begin | begin | begin | |
insert ignore test_test (id, version) values (5, 1); | 成功 | ||
insert ignore test_test (id, version) values (5, 1); | 把t1的隐私锁升级为X锁,t2进去S锁等待 | ||
insert ignore test_test (id, version) values (5, 1); | t3进入S锁等待 | ||
rollback | t1回滚后,释放X锁,t2、t3同时拿到了S锁 | ||
ok | deadlock | t2和t3都想拿到插入意向X锁,造成死锁条件 |
一个已提交但是未purge掉的记录会造成后续插入获取S共享锁,两个事务同时获取S锁,然后尝试获取插入意向锁,造成死锁
首先,为待插入的间隙加插入意向锁(Insert Intention Locks)。
- 如果该间隙已存在 GAP 锁或 Next-Key 锁,则加锁失败并进入等待状态。
- 否则,加锁成功,表示此间隙允许插入。
接下来,判断插入的记录是否具有唯一键。
-
若没有唯一键,直接进行插入操作。
-
若存在唯一键,进行唯一性约束检查。
-
若不存在相同的键值,完成插入操作。
-
若存在相同的键值:
-
判断该键值是否已被锁定。
-
若未被锁定:
-
判断该记录是否被标记为删除。
-
若未标记删除,报错 1062(duplicate key)。
-
若标记为删除,事务可能正在进行但尚未提交,因此加 S 锁并等待。
-
-
-
若已被锁定:
- 表示该记录正在处理(插入、删除或更新),且事务未提交,加 S 锁并等待。
-
-
-
对插入的记录加 X 记录锁。