我正在尝试调试以下场景:有两个并发进程,将完全相同的行插入到具有唯一约束的表中。这是在显式事务之外完成的(虽然我假设InnoDB将其作为单个语句处理内部自动提交?)
架构如下:
CREATE TABLE locks (
id bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT,
lock_uid varchar(255) NOT NULL,
count smallint(6) NOT NULL,
processor_id varchar(255) DEFAULT NULL,
created_at timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
updated_at timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
PRIMARY KEY (id),
UNIQUE INDEX locks_lock_uid_unique (lock_uid)
)
正如您所看到的,lock_uid
上有唯一索引,以防止表格中有多个具有相同值的行。
正在运行的命令(对于上下文,这些是从通用查询日志中获取的,以获得完整的健全性,在排序规则命令之外的任何一个线程上都没有其他语句):
主题1:
insert into `locks` (`lock_uid`, `count`, `processor_id`, `created_at`, `updated_at`)
values ('11161567', '0', NULL, '2017-11-07 10:46:36', '2017-11-07 10:46:36')
主题2:
insert into `locks` (`lock_uid`, `count`, `processor_id`, `created_at`, `updated_at`)
values ('11161567', '0', NULL, '2017-11-07 10:46:36', '2017-11-07 10:46:36')
这导致以下死锁:
LATEST DETECTED DEADLOCK
------------------------
2017-11-07 10:46:36 0x2ac88f791700
*** (1) TRANSACTION:
TRANSACTION 6089510736, ACTIVE 0 sec inserting
mysql tables in use 1, locked 1
LOCK WAIT 4 lock struct(s), heap size 1136, 4 row lock(s), undo log entries 1
MySQL thread id 177584962, OS thread handle 47059008030464, query id 13109086103 ec2-34-232-58-13.compute-1.amazonaws.com 34.232.58.13 appserver update
insert into `locks` (`lock_uid`, `count`, `processor_id`, `created_at`, `updated_at`) values ('11161567', '0', NULL, '2017-11-07 10:46:36', '2017-11-07 10:46:36')
*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 6403 page no 4 n bits 176 index locks_lock_uid_unique of table `core`.`locks` trx id 6089510736 lock_mode X locks gap before rec insert intention waiting
Record lock, heap no 107 PHYSICAL RECORD: n_fields 2; compact format; info bits 32
0: len 30; hex 636865636b6f75745265636f7665727950726f636573735f313131363137; asc 111617; (total 32 bytes);
1: len 8; hex 0000000003266637; asc &f7;;
*** (2) TRANSACTION:
TRANSACTION 6089510734, ACTIVE 0 sec inserting
mysql tables in use 1, locked 1
4 lock struct(s), heap size 1136, 5 row lock(s), undo log entries 1
MySQL thread id 177584971, OS thread handle 47040888903424, query id 13109086092 ec2-34-237-3-244.compute-1.amazonaws.com 34.237.3.244 appserver update
insert into `locks` (`lock_uid`, `count`, `processor_id`, `created_at`, `updated_at`) values ('11161567', '0', NULL, '2017-11-07 10:46:36', '2017-11-07 10:46:36')
*** (2) HOLDS THE LOCK(S):
RECORD LOCKS space id 6403 page no 4 n bits 176 index locks_lock_uid_unique of table `core`.`locks` trx id 6089510734 lock mode S
Record lock, heap no 104 PHYSICAL RECORD: n_fields 2; compact format; info bits 32
0: len 30; hex 636865636b6f75745265636f7665727950726f636573735f313131363135; asc 111615; (total 32 bytes);
1: len 8; hex 0000000003266632; asc &f2;;
Record lock, heap no 105 PHYSICAL RECORD: n_fields 2; compact format; info bits 32
0: len 30; hex 636865636b6f75745265636f7665727950726f636573735f313131363135; asc 111615; (total 32 bytes);
1: len 8; hex 0000000003266634; asc &f4;;
Record lock, heap no 107 PHYSICAL RECORD: n_fields 2; compact format; info bits 32
0: len 30; hex 636865636b6f75745265636f7665727950726f636573735f313131363137; asc 111617; (total 32 bytes);
1: len 8; hex 0000000003266637; asc &f7;;
*** (2) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 6403 page no 4 n bits 176 index locks_lock_uid_unique of table `core`.`locks` trx id 6089510734 lock_mode X locks gap before rec insert intention waiting
Record lock, heap no 107 PHYSICAL RECORD: n_fields 2; compact format; info bits 32
0: len 30; hex 636865636b6f75745265636f7665727950726f636573735f313131363137; asc 111617; (total 32 bytes);
1: len 8; hex 0000000003266637; asc &f7;;
*** WE ROLL BACK TRANSACTION (2)
我已经阅读了类似的答案(例如MySQL locking in Duplicate Key Error),但我不太清楚在这种情况下发生了什么。解释与死锁输出不匹配。
答案 0 :(得分:3)
为什么在事务外的2个插入处发生此死锁?
它仍有交易 https://dev.mysql.com/doc/refman/5.7/en/innodb-autocommit-commit-rollback.html
在InnoDB中,所有用户活动都发生在事务中。如果 启用自动提交模式,每个SQL语句形成一个 交易本身
因此,您的单个查询可被视为短期交易
为什么T2在请求X之前已经拥有S锁,而在相同的T1没有?
它有。这就是打印最新死锁信息的功能如何工作:-) 它不会打印第一个事务所持有的锁。您可以通过模拟来自2个并行mysql会话的简单死锁来自行检查。 这是代码:
https://github.com/mysql/mysql-server/blob/5.7/storage/innobase/lock/lock0lock.cc#L7236
DeadlockChecker::notify(const lock_t* lock) const
{
ut_ad(lock_mutex_own());
start_print();
print("\n*** (1) TRANSACTION:\n");
print(m_wait_lock->trx, 3000);
print("*** (1) WAITING FOR THIS LOCK TO BE GRANTED:\n");
print(m_wait_lock);
print("*** (2) TRANSACTION:\n");
print(lock->trx, 3000);
print("*** (2) HOLDS THE LOCK(S):\n");
print(lock);
/* It is possible that the joining transaction was granted its
lock when we rolled back some other waiting transaction. */
if (m_start->lock.wait_lock != 0) {
print("*** (2) WAITING FOR THIS LOCK TO BE GRANTED:\n");
print(m_start->lock.wait_lock);
}
DBUG_PRINT("ib_lock", ("deadlock detected"));
}
说明与死锁输出
不匹配它与您案件中似乎发生的情况相当接近。 这是一个模拟:
的制备:将
create table test (id int primary key, val int not null unique) engine=innodb;
insert into test values (1, 1), (2, 2), (3, 3);
现在让我们打开3个shell并运行以下命令:
1> begin;
2> begin;
3> begin;
然后
1>insert into test values (1, 1);
2>insert into test values (1, 1); (will hang)
3>insert into test values (1, 1); (will hang)
现在
1>rollback
2>would produce: ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction
3>would produce: Query OK, 1 row affected
1>show engine innodb status;
...
------------------------
LATEST DETECTED DEADLOCK
------------------------
2017-11-15 23:21:47 0x700000d95000
*** (1) TRANSACTION:
TRANSACTION 2336, ACTIVE 8 sec inserting
mysql tables in use 1, locked 1
LOCK WAIT 4 lock struct(s), heap size 1136, 2 row lock(s)
MySQL thread id 8, OS thread handle 123145316831232, query id 58 localhost 127.0.0.1 root update
insert into test values (1, 1)
*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 26 page no 3 n bits 72 index PRIMARY of table `test`.`test` trx id 2336 lock_mode X insert intention waiting
Record lock, heap no 1 PHYSICAL RECORD: n_fields 1; compact format; info bits 0
0: len 8; hex 73757072656d756d; asc supremum;;
*** (2) TRANSACTION:
TRANSACTION 2335, ACTIVE 13 sec inserting
mysql tables in use 1, locked 1
4 lock struct(s), heap size 1136, 2 row lock(s)
MySQL thread id 5, OS thread handle 123145316552704, query id 57 localhost root update
insert into test values (1, 1)
*** (2) HOLDS THE LOCK(S):
RECORD LOCKS space id 26 page no 3 n bits 72 index PRIMARY of table `test`.`test` trx id 2335 lock mode S
Record lock, heap no 1 PHYSICAL RECORD: n_fields 1; compact format; info bits 0
0: len 8; hex 73757072656d756d; asc supremum;;
*** (2) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 26 page no 3 n bits 72 index PRIMARY of table `test`.`test` trx id 2335 lock_mode X insert intention waiting
Record lock, heap no 1 PHYSICAL RECORD: n_fields 1; compact format; info bits 0
0: len 8; hex 73757072656d756d; asc supremum;;
*** WE ROLL BACK TRANSACTION (2)
你可以看到它接近你的结果
答案 1 :(得分:0)
插入查询在分配共享OR独占锁之前使用插入意图锁。
例如: 架构:
CREATE TABLE `temp_table` (
`id` INT NOT NULL AUTO_INCREMENT,
`u_id` INT NULL DEFAULT NULL,
PRIMARY KEY (`id`),
INDEX `u_id` (`u_id`)
)
COLLATE='latin1_swedish_ci'
ENGINE=InnoDB;
让我在表中有2,4,7 u_id。现在我正在从另一个会话中执行两个插入操作。
以下是可能的情况。
情况1:
T1:插入temp_table(u_id)值('5');
T2:插入temp_table(u_id)值('6');
此事务首先应用间隙插入意图锁定并检查应分配哪种类型的锁定(S或X)。就像这里5& 6是适合间隙(4-7)所以专用锁定将分配给T1& T2。
情况2:
T1:插入temp_table(u_id)值('5');
T2:插入temp_table(u_id)值('5');
这里虽然插入意图锁定T1将适合间隙(4-7)并且它将分配独占锁定但是T2不适合间隙作为间隙更新为(5-7)因为我们已经分配了独占锁定T1因此T2被分配了共享锁并等待T1完成。
如果T1成功完成,那么T2将被视为重复键错误,但如果由于任何原因T1失败,则T2将被独占锁定并尝试插入记录。
有关更多信息,请参阅Mysql指南: https://dev.mysql.com/doc/refman/5.7/en/innodb-locking.html