我有一个相互约束的问题。 我想要两个表,每个表都对另一个表有约束。
我正在使用 Doctrine2(但它与问题无关),这是我的简化代码:
SQL:
CREATE TABLE IF NOT EXISTS `thread` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`last_message_id` int(11) DEFAULT NULL,
`subject` varchar(255) NOT NULL
PRIMARY KEY (`id`),
UNIQUE KEY `UNIQ_C023F2BBBA0E79C3` (`last_message_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
ALTER TABLE `thread`
ADD CONSTRAINT `FK_C023F2BBBA0E79C3` FOREIGN KEY (`last_message_id`) REFERENCES `message` (`id`);
CREATE TABLE IF NOT EXISTS `message` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` int(11) DEFAULT NULL,
`thread_id` int(11) DEFAULT NULL,
`body` longtext NOT NULL
PRIMARY KEY (`id`),
KEY `IDX_9E4E8B5FA76ED395` (`user_id`),
KEY `IDX_9E4E8B5FE2904019` (`thread_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
ALTER TABLE `message`
ADD CONSTRAINT `FK_9E4E8B5FE2904019` FOREIGN KEY (`thread_id`) REFERENCES `thread` (`id`) ON DELETE CASCADE;
Doctrine2 映射(生成上面的 SQL 代码):
<?php
class Thread
{
/* @ORM\OneToOne() */
private $lastMessage;
}
class Message
{
/* @ORM\ManyToOne() */
private $thread;
}
当我尝试删除线程或消息时,我得到(逻辑上)错误:
违反完整性约束:1451 无法删除或更新父行:外键约束失败('thread',CONSTRAINT 'FK_C023F2BBBA0E79C3' FOREIGN KEY('last_message_id')REFERENCES 'message'('id'))
那么,有没有办法避免这个错误呢? 或者我应该忘记相互约束? 有什么事吗?
我想补充一点,我想保留 last_message_id,因为我想显示线程及其最后一条消息的信息,并且在不引用最后一条消息的情况下进行(分页)查询完全是一场噩梦...
谢谢!
最佳答案
FOREIGN KEY
约束中的循环路径很难处理,您的问题就是一个例子。如果你能避免它们,那就去做吧。这是重新设计表格的一种方法:
首先,在 (thread_id, message_id)
上的表 message
中添加一个 UNIQUE KEY
(或者使它成为主键,如果 Doctrine 可以这样做。这意味着 - 对于 MySQL - message(id)
不会自动递增,而是由 ORM 生成。如果您计划让应用程序访问直接或通过其他 ORM 数据库)。
然后通过复合 (thread_id, message_id)
将 last_message_id
移动到一个与 message
具有一对一关系的新表>。在此表中,thread_id
将是唯一的,因此每个线程都只有一条最后消息。
我将在这里编写 SQL 代码。此页面将帮助您处理可能会产生略有不同结构的 Doctrine 代码:Compound Primary and Foreign Keys
CREATE TABLE IF NOT EXISTS `thread` (
`id` int(11) NOT NULL AUTO_INCREMENT,
---`last_message_id` int(11) DEFAULT NULL, --- REMOVED: last_message
`subject` varchar(255) NOT NULL
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE IF NOT EXISTS `message` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` int(11) DEFAULT NULL,
`thread_id` int(11) NOT NULL, --- why was it NULL ?
`body` longtext NOT NULL
PRIMARY KEY (`id`),
KEY `IDX_9E4E8B5FA76ED395` (`user_id`),
---KEY `IDX_9E4E8B5FE2904019` (`thread_id`), --- REMOVED, not needed any more
--- because we have a this key
UNIQUE KEY (thread_id, id) --- ADDED, needed for the FK below
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
ALTER TABLE `message`
ADD CONSTRAINT `FK_9E4E8B5FE2904019`
FOREIGN KEY (`thread_id`)
REFERENCES `thread` (`id`)
ON DELETE CASCADE;
还有新表,用于存储每个线程的最后一条消息:
CREATE TABLE IF NOT EXISTS `thread_last_message` (
`message_id` int(11) NOT NULL,
`thread_id` int(11) NOT NULL,
PRIMARY KEY (`thread_id`),
KEY (`thread_id`, message_id`),
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
ALTER TABLE `thread_last_message` --- which just means
ADD CONSTRAINT `FK_something` --- that every
FOREIGN KEY (`thread_id`, `message_id`) --- thread's last message
REFERENCES `message` (`thread_id`, `id`) --- is a message
ON DELETE CASCADE;
另一种可能性是让 thread(last_message_id)
列为 NULL
并适当更改 FK 约束(如@Eric 的建议)。这在设计阶段不那么繁琐,而且您要处理的表也少了。在这种方法中,您必须小心插入和删除的顺序 - 正如您的示例所示。
作为第三种选择,您是否考虑过您的表中是否真的需要一个thread(last_message_id)
列?这难道不是一个计算值(来自两个表)而您跳过了整个问题吗?如果它是 best_message_id
我会理解这一点,但最后一条消息只是另一个表中的最后一行,按时间排序。您可以通过查询找到它,并且不需要(再次)将其存储在数据库中,除非有性能原因。
关于mysql - SQL 问题,相互约束, "a foreign key constraint fails",我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/9947981/