【发布时间】:2017-10-05 06:44:42
【问题描述】:
背景:
在 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 隔离级别,并执行以下命令:
- T1:开始;
- T2:开始;
- T2 : select * from test where name='eva' 进行更新;
- T1:选择 * from test where name='eva' 进行更新; (现在 T1 成立)
- 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”上的 next-key 锁(负无穷大,“eva”]
4. T1 : select * from test where name='eva' for update;
第 4 步需要:IX 锁,索引“name”上的下一个键锁(负无穷大,“eva”) IX 锁与 IX 兼容,因此 T1 无需 T2 释放即可获得它。 next-key lock实际上包含两部分:记录X + gap,由于不同的事务可以在一个gap上持有冲突锁,所以T1也持有gap lock而不等待T2。所以T1只等待T2 释放记录锁(二级索引 name='eva' 和聚集索引 id='1')继续。
5. T2 : insert into test values (1, 'eva'); (dead lock, T1 is rolled back)
第五步:这里的insert需要insert intension lock,这与gap lock不兼容。所以T2正在等待T1释放它的gap lock。但与此同时,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
【问题讨论】:
-
如果允许插入 0 或 1,主键 2 之前的间隙将不再存在。如果插入 0,它将变为 2 个间隙,一个在 0 之前,一个在 2 之前。 . 如果你插入 1 它变成一个不同的间隙,从 infimum 到 1 之前。
标签: mysql innodb deadlock database-deadlocks