最近删除记录时遇到死锁(注意隔离级别是可重复读取, MySQL 5.7)
这是重现步骤
1 创建一个新表
CREATE TABLE `t` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`name` varchar(32) NOT NULL,
PRIMARY KEY (`id`),
KEY `p_name` (`name`)
) ENGINE=InnoDB CHARSET=utf8;
2 准备3条记录
insert into t (name) value ('A'), ('C'), ('D');
3
+====================================+============================================================+
| Session A | Session B |
+====================================+============================================================+
| begin; | |
+------------------------------------+------------------------------------------------------------+
| | begin; |
+------------------------------------+------------------------------------------------------------+
| delete from t where name = 'C'; | |
+------------------------------------+------------------------------------------------------------+
| | delete from t where name = 'C'; --Blocked! |
+------------------------------------+------------------------------------------------------------+
| insert into t (name) values ('B'); | |
+------------------------------------+------------------------------------------------------------+
| | ERROR 1213 (40001): Deadlock found when trying to get lock |
+------------------------------------+------------------------------------------------------------+
的结果显示引擎 innodb 状态如下所示(最新检测到的死锁部分)
LATEST DETECTED DEADLOCK
------------------------
*** (1) TRANSACTION:
TRANSACTION 3631, ACTIVE 21 sec starting index read
mysql tables in use 1, locked 1
LOCK WAIT 2 lock struct(s), heap size 1136, 1 row lock(s)
MySQL thread id 13, OS thread handle 123145439432704, query id 306 localhost root updating
delete from t where name = 'C'
*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 69 page no 4 n bits 72 index p_name of table `jacky`.`t` trx id 3631 lock_mode X waiting
Record lock, heap no 4 PHYSICAL RECORD: n_fields 2; compact format; info bits 32
0: len 1; hex 43; asc C;;
1: len 8; hex 8000000000000018; asc ;;
*** (2) TRANSACTION:
TRANSACTION 3630, ACTIVE 29 sec inserting
mysql tables in use 1, locked 1
5 lock struct(s), heap size 1136, 4 row lock(s), undo log entries 2
MySQL thread id 14, OS thread handle 123145439711232, query id 307 localhost root update
insert into t (name) values ('B')
*** (2) HOLDS THE LOCK(S):
RECORD LOCKS space id 69 page no 4 n bits 72 index p_name of table `jacky`.`t` trx id 3630 lock_mode X
Record lock, heap no 4 PHYSICAL RECORD: n_fields 2; compact format; info bits 32
0: len 1; hex 43; asc C;;
1: len 8; hex 8000000000000018; asc ;;
*** (2) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 69 page no 4 n bits 72 index p_name of table `jacky`.`t` trx id 3630 lock_mode X locks gap before rec insert intention waiting
Record lock, heap no 4 PHYSICAL RECORD: n_fields 2; compact format; info bits 32
0: len 1; hex 43; asc C;;
1: len 8; hex 8000000000000018; asc ;;
如Innodb状态所示,会话B正在等待next-key lockC,并且会话A持有记录锁C并等待间隙锁定C;
众所周知
DELETE FROM ... WHERE ... 对搜索遇到的每个记录设置独占的下一键锁定
下一个键锁是索引记录上的记录锁和索引记录之前的间隙上的间隙锁的组合。
Q1:我猜测会话B是否首先获得间隙锁(next-key的一部分),然后等待记录锁。因此,会话A中的后者插入被会话B阻塞(由于间隙锁),最终导致死锁。正确的?
Q2: 作为C从索引中清除,会话 B 持有的间隙锁是否应该为 ('A', 'D')?如果是这样,为什么会话 A 正在等待范围(,'C')上的插入意图锁?
Q3: 为什么会话B有1 row lock(s)
,并且会话 A 有4 row lock(s)
?
Q4:改变索引时p_name
对于唯一索引,我们仍然会由于间隙锁而陷入死锁,这很奇怪。和官方的表现不一样doc https://dev.mysql.com/doc/refman/8.0/en/innodb-locks-set.html其中指出仅需要记录锁定。
DELETE FROM ... WHERE ... 对搜索遇到的每个记录设置独占的下一键锁定。然而,对于使用唯一索引锁定行来搜索唯一行的语句,只需要索引记录锁.
不过使用主键的时候就可以了id
执行删除(步骤如下所示)。这是 MySQL 中的错误吗?
1 准备数据
delete from t;
insert into t (id, name) value (1, 'A'), (3, 'C'), (5, 'D');
2
+-------------------------------------------+--------------------------------------+
| Session A | Session B |
+-------------------------------------------+--------------------------------------+
| begin; | |
| | begin; |
| delete from t where id = 3; | |
| | delete from t where id = 3; Blocked! |
| insert into t (id, name) values (2, 'B'); | |
| | |
| commit; | |
+-------------------------------------------+--------------------------------------+