微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

为什么在这种 MySQL 场景中我需要在 delete 语句之前使用无用的 insert 语句来防止死锁,有没有更好的方法?

如何解决为什么在这种 MySQL 场景中我需要在 delete 语句之前使用无用的 insert 语句来防止死锁,有没有更好的方法?

我遇到过这样一种情况,我认为,在 MysqL 中使用外键从表中删除和插入会导致间隙锁发生,从而导致死锁情况。我正在尝试解决如何解决这个问题,我想出的唯一解决方案是在删除和插入之前进行额外的插入。我想知道是否有人可以解释为什么需要这些无用的插入以及为什么“选择更新”没有类似地锁定行。我使用的是 MysqL 版本 5.7.23。

示例架构和初始行:​​

CREATE TABLE parent (
  id INT UNSIGNED NOT NULL PRIMARY KEY
) ENGINE=InnoDB;

CREATE TABLE child (
  name VARCHAR(127) NOT NULL,parent INT UNSIGNED NOT NULL,FOREIGN KEY (parent) REFERENCES parent(id)
) ENGINE=InnoDB;

INSERT INTO parent VALUES (1);
INSERT INTO parent VALUES (2);

在我的代码中,我需要用一组新的子项(零行或多行)替换目标父项的所有当前子项,并且该目标父项可能存在也可能不存在子项。当前代码如下所示,并且当这些特定父项的表中没有子项时会导致死锁:

transaction a:
BEGIN WORK;
DELETE FROM child WHERE parent=1;

transaction b:
BEGIN WORK;
DELETE FROM child WHERE parent=2;

transaction a:
INSERT INTO child VALUES ('a',1);
-- client a hangs waiting for lock

transaction b:
INSERT INTO child VALUES ('b',2);
-- client b aborts: ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction

如果您在不同的客户端会话中按此顺序运行这些事务语句,这将可靠地触发死锁。我相信这是因为间隙锁定。当我尝试将事务切换到 READ COMMITTED 隔离级别时,它将避免死锁,但如果两个事务在同一父级的子级上操作,则可能会出现幻像行。

删除之前为父级插入无用的行似乎可以修复死锁。以下场景不存在死锁:

transaction a:
BEGIN WORK;
INSERT INTO child VALUES ('fake',1);
DELETE FROM child WHERE parent=1;

transaction b:
BEGIN WORK;
INSERT INTO child VALUES ('fake',2);
DELETE FROM child WHERE parent=2;
-- client b hangs waiting for lock

transaction a:
INSERT INTO child VALUES ('a',1);
COMMIT;
-- no deadlock; client b Now has lock

transaction b:
INSERT INTO child VALUES ('b',2);
COMMIT;

我想也许不是这个插入,我可以替换一个 select 语句来获得与无用插入相同的锁,但以下并不能防止死锁:

transaction a:
BEGIN WORK;
SELECT * FROM child WHERE parent=1 FOR UPDATE;
DELETE FROM child WHERE parent=1;

transaction b:
BEGIN WORK;
SELECT * FROM child WHERE parent=2 FOR UPDATE;
DELETE FROM child WHERE parent=2;

transaction a:
INSERT INTO child VALUES ('a',2);
-- client b aborts: ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction

为什么删除语句和“选择更新”语句都不会检索与插入无用行相同的锁,是否有更好的方法来完成此任务并避免死锁?请记住,子表可能有也可能没有目标父级已经存在的一行或多行,我想删除目标父级的子表中的所有当前行,并替换为零个或多个新子级.谢谢!

解决方法

如果 PRIMARY KEY 上没有 Child,大多数操作都需要进行全表扫描。

无论如何,您的代码必须为死锁做好准备,即使您可能能够避免大多数死锁。

一般来说,处理死锁的方法是在每个 SQL 语句之后进行测试。当发生这种情况时,重新开始交易。 (第二次尝试很可能会避免导致它的其他连接。)

请注意,这意味着交易应编码为“短”。事务时间越长,其他连接被阻塞的可能性就越大。

,

有两件事。首先,您绝对应该为子表定义一个主键。当没有时,MySQL 会尝试识别可用于索引的字段,如果没有找到,则会生成一个内部字段。这将导致开销并且只是一个坏习惯。例如,在此处https://vettabase.com/blog/why-tables-need-a-primary-key-in-mariadb-and-mysql/中有更多关于此的信息。

第二,也是最重要的一点是,你为什么要使用两个事务?如果您要编辑同一个表并将两个操作作为原子事务执行,请在一次提交内执行:

BEGIN WORK;

DELETE FROM child WHERE parent=1;
DELETE FROM child WHERE parent=2;

INSERT INTO child VALUES ('a',1);

INSERT INTO child VALUES ('b',2);

COMMIT;

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。