mysql - SQL 问题,相互约束, "a foreign key constraint fails"

标签 mysql sql orm symfony doctrine-orm

我有一个相互约束的问题。 我想要两个表,每个表都对另一个表有约束。

我正在使用 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/

相关文章:

php - 从具有相同名称但不同表的两列中检索数据

mysql查询状态

php - 从数据库加载评论

sql - 如何检查oracle sql中的非空列约束?

mysql - 如何在请求其中一个等于的情况下获取不同值的计数

sql - 仅在哪里查询?

python - 每次将模型保存在 Django 上时,如何为模型的属性设置特定值?

mysql - 如何在(MYSQL和SQL Server)多列中搜索一个值而无需迭代/循环/游标?

sql-server - SQL Server 的 ORM 推荐

java - 在 mysql hibernate 中保存子实体时忽略唯一约束