理解mariadb僵局

理解mariadb僵局,mariadb,mysql-python,Mariadb,Mysql Python,来自(稍微)更复杂的现实生活场景。我正在尝试使用一行来同步对另一(外部)资源的访问。它基本上是有效的,但我一直看到“僵局”,并试图了解原因 它至少在mariadb版本10.2.22(阿尔卑斯)和10.1.38(ubuntu)中出现 下表: CREATE TABLE dlist ( dnum INTEGER AUTO_INCREMENT PRIMARY KEY, dname VARCHAR(64), dlnum INTEGER, last_u

来自(稍微)更复杂的现实生活场景。我正在尝试使用一行来同步对另一(外部)资源的访问。它基本上是有效的,但我一直看到“僵局”,并试图了解原因

它至少在mariadb版本10.2.22(阿尔卑斯)和10.1.38(ubuntu)中出现

下表:

CREATE TABLE dlist (
       dnum INTEGER AUTO_INCREMENT PRIMARY KEY,
       dname VARCHAR(64),
       dlnum INTEGER,
       last_update DATETIME,
       CONSTRAINT UNIQUE dlist_nn (dname, dlnum));
我有几个并发进程正在插入/更新表中的行。同时,我有一个进程试图“窃取”最近未更新的行

插入:

INSERT INTO dlist (dname, dlnum, last_update)
       VALUES (%s, %s, NOW())
       ON DUPLICATE KEY
       UPDATE last_update = NOW(), dnum = LAST_INSERT_ID(dnum);
删除:

DELETE FROM dlist
       WHERE (NOW() - INTERVAL 20 SECOND) > last_update
       LIMIT 1
       RETURNING dnum, dname, dlnum;
问题是,我看到在插入和删除端都报告了相当频繁的死锁。mysql python报告的消息:

(1213, 'Deadlock found when trying to get lock; try restarting transaction')
我可以通过重试来解决这个问题,但为什么会发生这种情况?有没有办法重新构造SQL来防止它?我不明白为什么单次插入和/或删除会导致“死锁”

要复制的完整源代码(提供用户名、密码、数据库名作为参数):


似乎不需要
dnum
;摆脱它。对于PK,推广独特的:

PRIMARY KEY(dname, dlnum)
这可能对所述问题有帮助,也可能没有帮助。不,那么

DELETE
没有索引,因此它必须扫描表的很多部分,可能是全部。通过添加

INDEX(last_update)
更多

如果无法摆脱
dnum
,那么这可能会有所帮助:将索引集更改为

PRIMARY KEY(dname, dlnum),
INDEX(dnum),    -- This is the minimum to keep AUTO_INCREMENT happy
INDEX(last_update)

(我不知道这是否有助于进一步避免死锁。但是,当一个表有两个唯一键(PK+a unique)时,我发现了一些微妙的问题)。

似乎没有必要使用
dnum
;摆脱它。对于PK,推广独特的:

PRIMARY KEY(dname, dlnum)
这可能对所述问题有帮助,也可能没有帮助。不,那么

DELETE
没有索引,因此它必须扫描表的很多部分,可能是全部。通过添加

INDEX(last_update)
更多

如果无法摆脱
dnum
,那么这可能会有所帮助:将索引集更改为

PRIMARY KEY(dname, dlnum),
INDEX(dnum),    -- This is the minimum to keep AUTO_INCREMENT happy
INDEX(last_update)


(我不知道这是否有助于进一步避免死锁。但是,当一个表有两个唯一键(PK+a unique)时,我会发现一些微妙的问题。

当执行更新或删除时,它会锁定该表。不能进行其他操作。delete可能正在进行全表扫描,以查找符合where子句条件的记录。您可以将主键唯一性的责任转移到其他地方。从主键中删除自动增量。删除更新的ON DUPLICATE KEY子句。您的应用程序或mysql可以首先生成UUID,然后使用UUID作为dnum将记录插入表中。你为什么要不断地删除这些记录?您可以忽略旧的/无效的记录吗?您正在使用诸如returning子句之类的功能,这些功能仅在mariadb中可用,而在mysql中不可用。因此我删除了mysql标签,因为codr不适用于mysql。谢谢@Shadow没有意识到这是一个错误only@Noremac-InnoDB不会“锁定表”进行更新或删除。为什么UUIDs会有帮助?@RickJames正在扫描表并导致死锁。你不会说那是锁吧?他需要一个独特的值来跟踪记录。uuid/guid可以在插入之前以最小的冲突风险生成。它消除了对自动递增主键的需要。当执行更新或删除时,它会锁定表。不能进行其他操作。delete可能正在进行全表扫描,以查找符合where子句条件的记录。您可以将主键唯一性的责任转移到其他地方。从主键中删除自动增量。删除更新的ON DUPLICATE KEY子句。您的应用程序或mysql可以首先生成UUID,然后使用UUID作为dnum将记录插入表中。你为什么要不断地删除这些记录?您可以忽略旧的/无效的记录吗?您正在使用诸如returning子句之类的功能,这些功能仅在mariadb中可用,而在mysql中不可用。因此我删除了mysql标签,因为codr不适用于mysql。谢谢@Shadow没有意识到这是一个错误only@Noremac-InnoDB不会“锁定表”进行更新或删除。为什么UUIDs会有帮助?@RickJames正在扫描表并导致死锁。你不会说那是锁吧?他需要一个独特的值来跟踪记录。uuid/guid可以在插入之前以最小的冲突风险生成。它不需要自动递增的主键。
dnum
实际上是表提供的关键同步数据:即,它是控制对外部资源访问的索引。在实际代码中,它是通过
cursor.lastrowid
返回的。换句话说,每个插入器都试图创建/更新一个对
(dlname,dlnum)
唯一的外部资源。返回的
dnum
标识要用于该操作的名称。然后,删除端尝试“窃取”并使用其中一个未使用的。如果另一个插入器试图获取相同的
(dlname,dlnum)
,它只会启动一个新的插入器(使用一个新的
dnum
)。但是,
上次更新的索引为+1。有了这些,我再也看不到僵局了。我想知道,这是否真的“解决”了它,或者只是让它发生的可能性小得多。回答了我自己的问题:在运行了几分钟后,死锁仍然发生。(另外,我已将插入客户端的等待时间缩短为0.05秒,并在删除程序上添加了0.03秒的等待时间。)答案仍然很有用。@GilHamilton-感谢您对
索引(上次更新)
的反馈。您的经验与我的猜测一致。@GilHamilton-我还添加了另一个可能的提示。
dnum
实际上是表中提供的关键同步数据:即,它是控制对外部资源访问的索引。在实际代码中,它被返回vi