繁体   English   中英

选择更新和插入之间的 MySQL InnoDB 死锁

[英]MySQL InnoDB deadlock between select for update and insert

背景 :

MySQL 5.7.18 中,我有一个名为“test”的表,定义如下:

| test  | CREATE TABLE `test` (
  `id` varchar(255) NOT NULL,
  `name` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `test_name_x01` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 |

它只有 1 行:

id | name
2  | eva

现在我开始 2 个事务,都在REPEATABLE-READ隔离级别,并执行如下命令:

  1. T1:开始;
  2. T2:开始;
  3. T2 : select * from test where name='eva' for update;
  4. T1 : select * from test where name='eva' for update; (现在T1成立)
  5. T2 : 插入测试值 (1, 'eva'); 死锁,T1回滚)

InnoDB 日志:

------------------------
LATEST DETECTED DEADLOCK
------------------------
2017-05-06 20:24:28 0x126df8000
*** (1) TRANSACTION:
TRANSACTION 112142, ACTIVE 15 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 139, OS thread handle 4950491136, query id 997169 localhost root Sending data
select * from test where name='eva' for update
*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 26 page no 5 n bits 72 index test_name_x01 of table `promotion`.`test` trx id 112142 lock_mode X waiting
Record lock, heap no 3 PHYSICAL RECORD: n_fields 2; compact format; info bits 0
 0: len 3; hex 657661; asc eva;;
 1: len 1; hex 32; asc 2;;

*** (2) TRANSACTION:
TRANSACTION 112141, ACTIVE 19 sec inserting
mysql tables in use 1, locked 1
4 lock struct(s), heap size 1136, 4 row lock(s), undo log entries 1
MySQL thread id 138, OS thread handle 4947148800, query id 997170 localhost root update
insert into test values (1, 'eva')
*** (2) HOLDS THE LOCK(S):
RECORD LOCKS space id 26 page no 5 n bits 72 index test_name_x01 of table `promotion`.`test` trx id 112141 lock_mode X
Record lock, heap no 1 PHYSICAL RECORD: n_fields 1; compact format; info bits 0
 0: len 8; hex 73757072656d756d; asc supremum;;

Record lock, heap no 3 PHYSICAL RECORD: n_fields 2; compact format; info bits 0
 0: len 3; hex 657661; asc eva;;
 1: len 1; hex 32; asc 2;;

*** (2) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 26 page no 5 n bits 72 index test_name_x01 of table `promotion`.`test` trx id 112141 lock_mode X locks gap before rec insert intention waiting
Record lock, heap no 3 PHYSICAL RECORD: n_fields 2; compact format; info bits 0
 0: len 3; hex 657661; asc eva;;
 1: len 1; hex 32; asc 2;;

*** WE ROLL BACK TRANSACTION (1)

我的想法

根据我的研究,以下是我认为可能导致死锁的原因。 但是需要专业的确认。

1. T1 : begin;
2. T2 : begin;
3. T2 : select * from test where name='eva' for update; 

第 3 步需要:IX 锁,索引“name”上的下一个键锁(负无穷大,“eva”]

4. T1 : select * from test where name='eva' for update;

第4步需要:IX锁,索引'name'上的next-key锁(负无穷大,'eva'] IX锁与IX兼容,所以T1可以在没有T2释放的情况获取它。next -key锁实际上包含2个部分:记录X+gap,由于冲突的锁可以被不同的事务持有在一个gap上,T1也持有gap锁而不用等待T2。所以T1只是在等待T2释放记录锁(在二级索引name='eva'和clustered index id='1') 继续。

5. T2 : insert into test values (1, 'eva'); (dead lock, T1 is rolled back)

步骤5:这里的刀片需要插入强度锁,与间隙锁不兼容。 所以 T2 正在等待 T1 释放它的间隙锁。 但同时 T1 正在等待 T2 释放记录 X 锁。

================================================== ======================

更新,发现了更多我上面的解释无法解释的有趣事实。

此外,对于第 5 步,在尝试使用不同的 ID 值后,我有以下观察:

如果表预加载了具有不同 ID 且名称相同的多行“eva”,则仅当第 5 步中尝试插入的 ID 值小于所有现有行的最小 ID 时死锁才会重现。

例如预加载表

id | name
2  | eva
4  | eva

对于上面的第 5 步,

insert (0, 'eva') => deadlock 
insert (1, 'eva') => deadlock
insert (3, 'eva') => NO deadlock
insert (5, 'eva') => NO deadlock

不要存放数为VARCHARs不带引号。 这会挫败任何使用索引的尝试。

更多的

一般来说, ...

当可以通过索引识别单行时,只有该行将被“锁定”。 单行锁定通常通过“延迟”一个事务直到另一个事务释放它的锁避免死锁。

当必须扫描整个表时,可能需要锁定所有行。 可能不允许“延迟”,并陷入僵局。

任何状况之下, ...

通过准备好重放整个事务来计划死锁。 有些僵局是不可避免的。

(抱歉,含糊不清;这里有太多可能的变化。修复引号或索引;这应该可以避免大多数死锁。)

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM