主键会导致mysql死锁吗?

Posted

技术标签:

【中文标题】主键会导致mysql死锁吗?【英文标题】:can primary key cause mysql deadlock? 【发布时间】:2014-03-15 20:03:19 【问题描述】:

我最近遇到了一个奇怪的 mysql 死锁,我的表看起来像(为简单起见,我删除了不相关的列):

CREATE TABLE Node (
    `id` bigint unsigned NOT NULL UNIQUE AUTO_INCREMENT,
    `nodeId` varchar(128) NOT NULL UNIQUE,
    PRIMARY KEY  (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

CREATE TABLE  JobQueue (
    `id` bigint unsigned NOT NULL UNIQUE AUTO_INCREMENT,
    `workerManagementNodeId` varchar(32) DEFAULT NULL,
    CONSTRAINT `fkJbqMgmtNodeId` FOREIGN KEY (`workerManagementNodeId`) REFERENCES `Node` (`nodeId`) ON DELETE SET NULL,
    PRIMARY KEY  (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

当我的节点宕机时,它会删除节点表中的记录。此时,作业队列可能正在删除 JobQueue 表中具有 Node.nodeId 外键的队列。然后mysql抛出异常:

原因:com.mysql.jdbc.exceptions.jdbc4.MySQLTransactionRollbackException:尝试获取锁时发现死锁;尝试重启交易

我检查了数据库,JobQueue 已成功删除,但 Node 没有。我了解外键的顺序可能会导致死锁,但在我的情况下,节点表没有外键,只有主键。那么死锁怎么会发生呢?

顺便说一句:我很确定死锁是由 JobQueue 引起的,我花了很多时间缩小这个问题的范围,所以在我的测试中只使用这两个表。

更新:

CREATE TABLE  JobQueueEntry (
    `id` bigint unsigned NOT NULL UNIQUE AUTO_INCREMENT,
    `name` varchar(255) NOT NULL,
    `jobQueueId` bigint unsigned NOT NULL,
    `issuerManagementNodeId` varchar(32) DEFAULT NULL,
    PRIMARY KEY  (`id`),
    CONSTRAINT `fkJbqEtryMgmtNodeId` FOREIGN KEY (`issuerManagementNodeId`) REFERENCES `Node` (`nodeId`) ON DELETE SET NULL,
    CONSTRAINT `fkJobQueueId` FOREIGN KEY (`jobQueueId`) REFERENCES `JobQueue` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

我终于注意到这仍然是外键顺序引起的问题。实际上还有另一个表 JobQueueEntry 以相反的顺序同时具有 Node 和 JobQueue 的外键。所以当删除一个节点时,它会尝试更新 JobQueue 和 JobQueueEntry。发生死锁是因为 JobQueueEntry 在节点之前有 JobQueue 的外键。

感谢@ctrl 的回答!

【问题讨论】:

在您的系统处于负载状态时查看SHOW PROCESSLIST。您可能会看到很多积压的查询。 @tadman SHow PROCESSLIST 似乎没问题,连接数与连接池大小相匹配。积压的查询是什么意思? 在重负载下,MySQL 有时会因为“死锁”类型的消息导致查询失败,因为它无法锁定表以插入或更新数据。这只是一种猜测,因为您的架构看起来很简单。但是,更新索引可能是一项昂贵的操作,因此最好在发生这种情况时密切监视服务器行为,以便更好地了解发生这种情况的条件。你能可靠地复制它吗? @tadman 是的,系统负载很重,我启动了 1000 个线程来并行执行作业 【参考方案1】:

首先这应该是一个评论,但我现在没有足够的代表,所以......我的“评论”基于我的 Oracle 经验,但我认为这是一个常见问题,mysql 也可以在同样的方式。

由于您有一个 fk on delete set null,当您从 Node 中删除某些内容时,数据库引擎必须通过 JobQueue 来更新它,并且它可能会获取一个表锁来执行此操作(oracle 在您的情况下会这样做)。如果您有多个参与者,一些更新/删除 Jobs 表和一些更新/删除 JobsQueue 表,您最终可能会出现死锁。

在 Oracle 中,为了解决这个问题(并获得更好的性能),您通常会在子表的 fk 列上创建一个索引,在您的例子中是 workerManagementNodeId。

如果 mysql 以不同且更智能的方式执行此操作,请见谅 :)

【讨论】:

是的,我的测试放置了一个重型节点,其中 1000 个线程并行执行作业。 Job 表有很多更新,它也有 JobQueue 表的外键。但是索引如何解决这个问题?是否只是提高性能以减少发生死锁的机会? 这不仅仅是一个性能修复,它实际上改变了子表的锁定方式(行锁而不是全表锁)并防止死锁。您可以在hongwang.wordpress.com/2013/07/31/… 找到解释(再次...... Oracle 方面)。

以上是关于主键会导致mysql死锁吗?的主要内容,如果未能解决你的问题,请参考以下文章

mysql死锁

由于多线程插入导致 MySQL 死锁

问题解决关于MySQL在上行锁并更新数据之后导致死锁问题解决

问题解决关于MySQL在上行锁并更新数据之后导致死锁问题解决

mysql 发生死锁问题请求帮助

mysql 死锁排查