MySQL - 删除具有引用自身的外键约束的行

Alo*_*tan 16 mysql foreign-key database-design delete constraint

我有一个表格,其中存储了用户在我网站上发布的所有论坛消息。消息层次结构是使用嵌套集模型实现的

以下是该表的简化结构:

  • Id(主键)
  • Owner_Id(对Id 的外键参考)
  • Parent_Id(对Id 的外键引用)
  • 左左
  • 正确的
  • n级

现在,该表看起来像这样:

+ ------- + ------------- + -------------- + ---------- + ----------- + ----------- +
| Id      | Owner_Id      | Parent_Id      | nleft      | nright      | nlevel      |
+ ------- + ------------- + -------------- + ---------- + ----------- + ----------- +
| 1       | 1             | NULL           | 1          | 8           | 1           |
| 2       | 1             | 1              | 2          | 5           | 2           |
| 3       | 1             | 2              | 3          | 4           | 3           |
| 4       | 1             | 1              | 6          | 7           | 2           |
+ ------- + ------------- + -------------- + ---------- + ----------- + ----------- +
Run Code Online (Sandbox Code Playgroud)

注意第一行是根消息,这个帖子的树可以显示为:

-- SELECT * FROM forumTbl WHERE Owner_Id = 1 ORDER BY nleft;

MESSAGE (Id = 1)
    MESSAGE (Id = 2)
        Message (Id = 3)
    Message (Id = 4)
Run Code Online (Sandbox Code Playgroud)

当我尝试Owner_Id在单个查询中删除同一行下的所有行时,就会出现我的问题。例子:

DELETE FROM forumTbl WHERE Owner_Id = 1 ORDER BY nright;
Run Code Online (Sandbox Code Playgroud)

上述查询失败并出现以下错误:

错误代码:1451。无法删除或更新父行:外键约束失败(forumTbl,CONSTRAINT Owner_Id_frgnFOREIGN KEY ( Owner_Id) REFERENCES forumTbl( Id) ON DELETE NO ACTION ON UPDATE NO ACTION)

原因是第一行,即根节点Id=1),在其Owner_Id字段(Owner_Id=1)中也有相同的值,并且由于外键约束导致查询失败。

我的问题是:如何防止这种外键约束循环并删除引用自身的行?有没有办法在不必先将根行的更新为 的情况下做到这一点?Owner_IdNULL

我创建了这个场景的演示:http : //sqlfiddle.com/#!9/fd1b1

谢谢你。

ype*_*eᵀᴹ 11

  1. 除了禁用危险且可能导致不一致的外键外,还有两个其他选项需要考虑:

  2. FOREIGN KEY使用ON DELETE CASCADE选项修改约束。我还没有测试过所有的情况,但你肯定需要这个作为(owner_id)外键,也可能用于另一个。

    ALTER TABLE forum
        DROP FOREIGN KEY owner_id_frgn,
        DROP FOREIGN KEY parent_id_frgn ;
    ALTER TABLE forum
        ADD CONSTRAINT owner_id_frgn
            FOREIGN KEY (owner_id) 
            REFERENCES forum (id)
            ON DELETE CASCADE,
        ADD CONSTRAINT parent_id_frgn
            FOREIGN KEY (parent_id) 
            REFERENCES forum (id)
            ON DELETE CASCADE ;
    
    Run Code Online (Sandbox Code Playgroud)

    如果这样做,那么从树中删除一个节点和所有后代就更简单了。您删除一个节点并通过级联操作删除所有后代:

    DELETE FROM forum
    WHERE id = 1 ;         -- deletes id=1 and all descendants
    
    Run Code Online (Sandbox Code Playgroud)
  3. 你介入的问题实际上是2个问题。第一个是从具有自引用外键的表中删除对于 MySQL 来说不是一个严重的问题,只要没有引用自身的行即可。如果有一行,如在您的示例中,选项是有限的。禁用外键或使用CASCADE操作。但是如果没有这样的行,删除就成了一个小问题。

    因此,如果我们决定在 中存储NULL而不是相同id的内容owner_id,那么您可以在不禁用外键和级联的情况下进行删除。

    然后你会偶然发现第二个问题!运行您的查询会引发类似的错误:

    DELETE FROM forum 
    WHERE owner_id = 1 OR id = 1 ; 
    
    Run Code Online (Sandbox Code Playgroud)

    错误、警告:
    无法删除或更新父行:外键约束失败 (rextester.forum, CONSTRAINT owner_id_frgn FOREIGN KEY (owner_Id) REFERENCES forum (id))

    但是,此错误的原因将与以前不同。这是因为 MySQL 在删除每一行之后检查每个约束,而不是(应该)在语句的末尾。因此,当在删除其子项之前删除父项时,我们会收到外键约束错误。

    幸运的是,对此有一个简单的解决方案,感谢嵌套集模型和 MySQL 允许我们设置删除顺序。我们只需要按 bynleft DESC或 by排序nright DESC,这样可以确保在父级之前删除所有子级:

    DELETE FROM forum 
    WHERE owner_id = 1 OR id = 1 
    ORDER BY nleft DESC ; 
    
    Run Code Online (Sandbox Code Playgroud)

    小注意,我们可以(或应该)使用考虑嵌套模型的条件。这是等效的(并且可能使用索引(nleft, nright)来查找要删除的节点:

    DELETE FROM forum 
    WHERE nleft >= 1 AND nright <= 8 
    ORDER BY nleft DESC ; 
    
    Run Code Online (Sandbox Code Playgroud)


a_v*_*lad 8

SET FOREIGN_KEY_CHECKS=0;
DELETE FROM forum WHERE Owner_Id = 1 ORDER BY nright;
SET FOREIGN_KEY_CHECKS=1;
Run Code Online (Sandbox Code Playgroud)

只是不要忘记在这种情况下您必须手动解析 parent_id 显示为 1 时的情况,因为您没有使用级联