【发布时间】:2020-10-31 22:33:54
【问题描述】:
最近在删除记录的时候遇到了死锁(注意隔离级别是REPEATABLE READ,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 |
+------------------------------------+------------------------------------------------------------+
show engine innodb status的结果如下图(LATEST DETECTED DEADLOCK部分)
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 正在等待下一个键锁 C,会话 A 持有记录锁 C 并在 C 上等待间隙锁;
众所周知
DELETE FROM ... WHERE ... 在搜索遇到的每条记录上设置一个排他的下一个键锁定
next-key 锁是索引记录上的记录锁和索引记录之前的间隙上的间隙锁的组合。
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 不同,后者声明只需要记录锁。
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; | |
+-------------------------------------------+--------------------------------------+
【问题讨论】:
-
我不知道细节,但似乎在“间隙”中搞乱会导致比看起来最佳的锁更糟糕。在这种情况下,“间隙”可能是“A”和“D”之间的所有范围。忍受它。编写代码以重新启动事务。第二次尝试可能不会出现死锁。
标签: mysql database transactions innodb database-deadlocks