我在这里找到了一个类似的问题Deadlock using SELECT ... FOR UPDATE in MySQL。但答案并没有真正解释为什么会发生这种情况。
这种情况很容易重现@ Mysql 5.7.17(或5.5或5.6中的其他版本):
CREATE TABLE `test` (
`id` bigint(11) NOT NULL AUTO_INCREMENT,
`val` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
PRIMARY KEY (`id`),
KEY `search` (`val`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
insert into test set val='pre-lock';
==会话1 ==
start transaction;
select * from test where val='pre-lock' for update;
==会话2 ==
start transaction;
select * from test where val='pre-lock' for update;
==会话1 ==
insert into test set val='/a/b/c'; //note that if the set val='pre-lock111', there will be no deadlock at session 2
==会话2 ==
ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction
show engine innodb status的结果:
LATEST DETECTED DEADLOCK
------------------------
2017-04-06 23:54:03 0x7000057db000
*** (1) TRANSACTION:
TRANSACTION 1333, ACTIVE 18 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 5, OS thread handle 123145394155520, query id 62 localhost root Sending data
select * from test where val='pre-lock' for update
*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 24 page no 4 n bits 72 index search of table `test_tnx`.`test` trx id 1333 lock_mode X waiting
Record lock, heap no 2 PHYSICAL RECORD: n_fields 2; compact format; info bits 0
0: len 8; hex 7072652d6c6f636b; asc pre-lock;;
1: len 8; hex 8000000000000001; asc ;;
*** (2) TRANSACTION:
TRANSACTION 1332, ACTIVE 29 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 62, OS thread handle 123145394434048, query id 63 localhost root update
insert into test set val='/a/b/c'
*** (2) HOLDS THE LOCK(S):
RECORD LOCKS space id 24 page no 4 n bits 72 index search of table `test_tnx`.`test` trx id 1332 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 2 PHYSICAL RECORD: n_fields 2; compact format; info bits 0
0: len 8; hex 7072652d6c6f636b; asc pre-lock;;
1: len 8; hex 8000000000000001; asc ;;
*** (2) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 24 page no 4 n bits 72 index search of table `test_tnx`.`test` trx id 1332 lock_mode X locks gap before rec insert intention waiting
Record lock, heap no 2 PHYSICAL RECORD: n_fields 2; compact format; info bits 0
0: len 8; hex 7072652d6c6f636b; asc pre-lock;;
1: len 8; hex 8000000000000001; asc ;;
*** WE ROLL BACK TRANSACTION (1)
我的看法是会话1中的insert子句获得了一个间隙锁定,它通过select for update以某种方式与间隙锁定重叠。因此,这会造成僵局。但是,我找不到任何支持我的想法的文件。请帮忙解释一下。
7日更新1。 2017年4月:
我的完整用例如下。每个会话都启动一个事务。它首先锁定“预锁定”行以获得唯一性,然后执行一组插入。然后,它提交所有插入并释放锁定。据我所知,如果我删除“预锁定”,两个事务可能会导致死锁,并且两个都失败(mysql杀死一个,让另一个按照@Michael - sqlbot指出的那样去)。同时,允许其他一些会话读取之前已提交的表的内容,因此,独占锁定表不是提示。
7日更新2。 2017年4月:
要完全解释我的目标,这是一个很长的故事。简而言之,我想使用mysql作为外部系统的锁定服务。我提到的这些插入是一组外部锁。由于我有读/写锁,并且想要支持通配符,所以它不能是唯一的。在这些插入之前,我实际上需要使用一组选择进行冲突分析。设置全局预锁定对我来说是一个很好的解决方案来完成这项任务。