我最近遇到了一个奇怪的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;
当我的节点发生故障时,它将删除Node表中的记录。此时,作业队列可能正在删除JobQueue表中具有Node.nodeId外键的队列。然后mysql抛出异常:
引起:com.mysql.jdbc.exceptions.jdbc4.MySQLTransactionRollbackException:尝试获取锁定时发现死锁;尝试重新启动交易
我检查了数据库,JobQueue已成功删除,但Node没有。我理解外键的顺序可能会导致死锁,但在我的情况下,Node表没有外键只有主键。那么死锁怎么会发生呢?
BTW:我很确定死锁是由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的回答!
答案 0 :(得分:1)
首先,这应该是一个评论,但我现在没有足够的代表,所以......我的“评论”基于我的Oracle经验,但我认为这是一个常见的问题,而且mysql可以表现为同样的方式。
由于你有一个fk on delete set null,当你从Node中删除一些东西时,db引擎必须通过JobQueue来更新它,并且它可能获得一个表锁来执行此操作(oracle在你的情况下执行它)。如果你有多个actor,一些更新/删除Jobs表和一些更新/删除JobsQueue表,你最终可能会遇到死锁。
在Oracle中,为了解决这个问题(并获得更好的性能),通常在子表的fk列上创建一个索引,在这种情况下是workerManagementNodeId。
如果mysql以不同且更聪明的方式做到这一点,请原谅:)