【发布时间】: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 个线程来并行执行作业