我正在使用MySQL 5.5。我注意到在并发场景中发生了一个特殊的死锁,我不认为这种僵局应该发生。
使用两个同时运行的mysql客户端会话重现:
mysql session 1 :
create table parent (id int(11) primary key);
insert into parent values (1);
create table child (id int(11) primary key, parent_id int(11), foreign key (parent_id) references parent(id));
begin;
insert into child (id, parent_id) values (10, 1);
-- this will create shared lock on parent(1)
mysql session 2 :
begin;
-- try and get exclusive lock on parent row
select id from parent where id = 1 for update;
-- this will block because of shared lock in session 1
mysql session 1 :
-- try and get exclusive lock on parent row
select id from parent where id = 1 for update;
-- observe that mysql session 2 transaction has been rolled back
mysql session 2 :
ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction
show engine innodb status
报告的信息是:
------------------------
LATEST DETECTED DEADLOCK
------------------------
161207 10:48:56
*** (1) TRANSACTION:
TRANSACTION 107E67, ACTIVE 43 sec starting index read
mysql tables in use 1, locked 1
LOCK WAIT 2 lock struct(s), heap size 376, 1 row lock(s)
MySQL thread id 13074, OS thread handle 0x7f68eccfe700, query id 5530424 localhost root statistics
select id from parent where id = 1 for update
*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 0 page no 3714 n bits 72 index `PRIMARY` of table `foo`.`parent` trx id 107E67 lock_mode X locks rec but not gap waiting
Record lock, heap no 2 PHYSICAL RECORD: n_fields 3; compact format; info bits 0
0: len 4; hex 80000001; asc ;;
1: len 6; hex 000000107e65; asc ~e;;
2: len 7; hex 86000001320110; asc 2 ;;
*** (2) TRANSACTION:
TRANSACTION 107E66, ACTIVE 52 sec starting index read
mysql tables in use 1, locked 1
5 lock struct(s), heap size 1248, 2 row lock(s), undo log entries 1
MySQL thread id 12411, OS thread handle 0x7f68ecfac700, query id 5530425 localhost root statistics
select id from parent where id = 1 for update
*** (2) HOLDS THE LOCK(S):
RECORD LOCKS space id 0 page no 3714 n bits 72 index `PRIMARY` of table `foo`.`parent` trx id 107E66 lock mode S locks rec but not gap
Record lock, heap no 2 PHYSICAL RECORD: n_fields 3; compact format; info bits 0
0: len 4; hex 80000001; asc ;;
1: len 6; hex 000000107e65; asc ~e;;
2: len 7; hex 86000001320110; asc 2 ;;
*** (2) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 0 page no 3714 n bits 72 index `PRIMARY` of table `foo`.`parent` trx id 107E66 lock_mode X locks rec but not gap waiting
Record lock, heap no 2 PHYSICAL RECORD: n_fields 3; compact format; info bits 0
0: len 4; hex 80000001; asc ;;
1: len 6; hex 000000107e65; asc ~e;;
2: len 7; hex 86000001320110; asc 2 ;;
*** WE ROLL BACK TRANSACTION (1)
您可以看到事务(1)没有显示已经获得的任何S或X锁;它只是被阻止试图获得一个独家锁定。由于没有周期,在这种情况下不应该像我理解的那样陷入僵局。
这是一个已知的MySQL错误吗?还有其他人遇到过吗?使用了哪些变通方法?
这些是我们可以采取的可能步骤:
我们还有其他选择吗?
答案 0 :(得分:6)
这是一个长期存在的错误,您可以阅读以下内容:This bug report
这是MySQL级表锁定中的一个问题。
在InnoDB内部,可以读取FOREIGN KEY约束检查(或者, 使用ON UPDATE或ON DELETE子句,写入父表或子表。
通常,表访问权限由以下锁控制: 1. MySQL元数据锁 2. InnoDB表锁 3. InnoDB记录锁
所有这些锁都会保留到交易结束。
在某些模式下会跳过InnoDB表和记录锁,但是 不在外国钥匙检查期间。死锁是因为MySQL造成的 仅为明确的表获取元数据锁 在SQL语句中提到。
我想解决方法可能是访问孩子(或父母) 在有问题的FOREIGN之前,交易开始时的表格 关键操作。
阅读讨论及其回复
答案 1 :(得分:2)
没有给出更新父行的原因, 但我认为这与某些de规范化有关,基于这个问题的序列:
-- session 1
begin;
insert into child (id, parent_id) values (10, 1);
...
select id from parent where id = 1 for update;
例如,订单(父表)具有列数量, 维持为所有订单行数量的总和(子项 表)。
似乎维护父数据的逻辑在应用程序中编码 本身(使用显式更新语句),具有以下结果:
如果在很多不同的地方插入儿童, 然后必须在所有这些地方更新客户端中的应用程序逻辑 保持诚信。这是代码重复。
即使这只在一个地方完成,也就是父表的事实 在添加子项时,服务器无法查找,需要更新。
相反,请考虑以下选项:
在子表上定义触发器,根据需要更新父表。
它具有以下含义:
首先,维护父表的逻辑不再(可能) 重复,因为它在触发器本身。
其次,这是重要的部分,MySQL服务器现在知道了 每当插入子记录时,表父级都会更新,因为 对此,采取正确的锁定(独占而不是共享)。
使用8.0测试,见下文。
关于并发吞吐量的关注,
将在不同的父行上执行不同的事务 并行,因为在父(不同)行上采用了独占锁,而不是 父表。
确实会在同一父行上并发运行的事务 被序列化......这实际上是预期的结果,因为它们完成了 无论如何都是相同的记录。
序列化保证成功的事务应该提供更好的吞吐量(就应用程序工作负载而言) 某些事务失败,只能重试它们。
显然,还需要更新和删除触发器,以便根据应用程序逻辑更新父级。
<强>设置强>
create table parent (
id int(11) primary key,
number_of_children int(11));
create table child (
id int(11) primary key,
parent_id int(11),
foreign key (parent_id) references parent(id));
delimiter $$;
create trigger bi_child before insert on child
for each row
begin
update parent
set number_of_children = number_of_children + 1
where id = NEW.parent_id;
end
$$
delimiter ;$$
begin;
insert into parent values (1, 0);
insert into parent values (2, 0);
commit;
第1节
begin;
insert into child values (10, 1);
第2节
begin;
insert into child values (20, 2);
未阻止,因为使用了不同的父级。
第3节
begin;
-- this now blocks, waiting for an X lock on parent row 1.
insert into child values (11, 1);
第1节
-- unlocks session 3
commit;
第3节
提交;
第2节
提交;
<强>结果
select * from parent;
id number_of_children
1 2
2 1