外键约束:何时使用ON UPDATE和ON DELETE


195

我正在使用MySQL Workbench设计数据库架构,这很酷,因为您可以执行图并将其转换为:P

无论如何,由于它具有外键支持,所以我决定使用InnoDB。我注意到的一件事是,它允许您为外键设置On Update和Delete选项。有人可以在一个简单的示例中解释“限制”,“级联”和设置null的位置吗?

例如,假设我有一个user包含的表userID。说我有一个消息表message是一个多到许多有2个外键(其中引用同一个主键,userID在该user表)。在这种情况下,设置“更新时”和“删除时”选项是否有用?如果是这样,我该选择哪一个?如果这不是一个很好的例子,请您举一个很好的例子来说明这些例子如何有用?

谢谢

Answers:


485

不要犹豫,对数据库施加约束。您将确保具有一致的数据库,这是使用数据库的充分理由之一。尤其是当您有多个应用程序要求它时(或者只有一个应用程序,但是使用不同源的直接模式和批处理模式)。

使用MySQL时,您没有像postgreSQL中那样的高级约束,但是至少外键约束是相当高级的。

我们以一个公司表为例,该公司表的用户表包含这些公司的人员

CREATE TABLE COMPANY (
     company_id INT NOT NULL,
     company_name VARCHAR(50),
     PRIMARY KEY (company_id)
) ENGINE=INNODB;

CREATE TABLE USER (
     user_id INT, 
     user_name VARCHAR(50), 
     company_id INT,
     INDEX company_id_idx (company_id),
     FOREIGN KEY (company_id) REFERENCES COMPANY (company_id) ON...
) ENGINE=INNODB;

让我们看一下ON UPDATE子句:

  • ON UPDATE RESTRICT默认值:如果您尝试更新表COMPANY中的company_id,则如果一个USER至少链接到该公司,则引擎将拒绝该操作。
  • 更新时不采取任何措施:与限制相同。
  • ON UPDATE CASCADE通常是最好的:如果您在表COMPANY的一行中更新company_id,引擎将在引用该COMPANY的所有USER行上相应地对其进行更新(但不会在USER表上激活任何触发器,警告)。引擎会为您跟踪更改,这很好。
  • ON UPDATE SET NULL:如果您在表COMPANY的行中更新company_id,则引擎会将相关的用户company_id设置为NULL(在USER company_id字段中可用)。我看不到与此更新有关的任何有趣的事情,但我可能是错的。

现在在“ 删除”侧:

  • ON DELETE RESTRICT默认值:如果您尝试删除表COMPANY中的company_id ID,则如果一个USER至少可以链接到该公司,则引擎将拒绝该操作,从而可以挽救您的生命。
  • 删除时不采取行动:与限制相同
  • ON DELETE CASCADE危险:如果删除表COMPANY中的公司行,则引擎也将删除相关的USER。这很危险,但是可用于在辅助表上进行自动清理(因此,它可以是您想要的东西,但是肯定不能用于COMPANY <-> USER示例)
  • ON DELETE SET NULL少数:如果删除COMPANY行,则相关的USER将自动具有与NULL的关系。如果Null是没有公司的用户的价值,那么这可能是一个好习惯,例如,您可能需要将用户保留在您的应用程序中,作为某些内容的作者,但是删除公司对您来说不是问题。

通常我的默认设置是:ON DELETE RESTRICT ON UPDATE CASCADE。带有一些ON DELETE CASCADE跟踪表(日志-并非所有日志-之类的东西),以及ON DELETE SET NULL主表是包含外键的表的“简单属性”时,例如USER表的JOB表。

编辑

自从我写那已经很久了。现在,我想我应该添加一个重要警告。MySQL在级联方面有一个很大的限制。级联不是触发触发器。因此,如果您对该引擎使用触发器过于自信,则应避免级联约束。

MySQL触发器仅针对SQL语句对表所做的更改激活。它们不会为视图的更改而激活,也不会因未将SQL语句传输到MySQL服务器的API对表所做的更改而激活

==>请参阅最后一次编辑,此域正在发展

触发器不能通过外键动作激活。

而且我认为这一天不会解决。外键约束由InnoDb存储管理,触发器由MySQL SQL引擎管理。两者是分开的。Innodb是唯一具有约束管理的存储,也许有一天他们会直接在存储引擎中添加触发器,也许不是。

但是我对应该在糟糕的触发器实现和非常有用的外键约束支持之间选择哪个元素有自己的见解。一旦习惯了数据库一致性,您就会喜欢PostgreSQL。

12 / 2017-更新有关MySQL的此编辑:

如@IstiaqueAhmed在评论中所述,此问题的情况已发生变化。因此,请点击链接并检查最新的实际情况(将来可能会再次更改)。


8
ON DELETE CASCADE : dangerous-夹一点盐。
2011年

3
您需要小心级联,如果需要更改许多记录,它会锁定您的系统。特别是在使用Cascde删除之前,应仔细检查,如果有子记录,您通常确实希望删除不会发生。我不希望客户删除来擦除他以前拥有的Oreders的财务数据。有时最好确保未启用级联并提供一种使amrk记录处于非活动状态的方法。
HLGEM 2011年

1
就业务逻辑而言,在以下情况中可能有一种有趣SET NULL的情况ON UPDATE:更新公司表示公司>用户关系的分离。例如:如果公司更改了其业务类型,则以前的用户可能不再与该业务相关,因此NULL对于该索引而言可能更可取。
CPHPython '16

1
@regilero,似乎在你的第一个链接(内容dev.mysql.com/doc/refman/5.6/en/triggers.html)到MySQL的网站已经改变。它说的This includes changes to base tables that underlie updatable views不是您粘贴的内容,而是They do not activate for changes in views
Istiaque Ahmed

6
“我不希望客户删除自己以前所下订单的财务数据。” 在这种情况下,您可能仍然仍然需要客户的数据。您的设计可能应该将客户标记为不活动,而不是从数据库中删除他们的行。在实践中,这是我的专业经验,你实际上非常很少想删除任何东西,宁愿默认标记无效。在永久删除是可以的情况下,CASCADE DELETE通常也可以,甚至是首选。我认为这不是特别危险。
GrandOpener

3

@MarkR答案的补充-需要注意的一件事是,许多带有ORM的PHP框架不会识别或使用高级数据库设置(外键,级联删除,唯一约束),这可能会导致意外行为。

例如,如果您使用ORM DELETE CASCADE删除记录,并且将删除相关表中的记录,那么ORM试图删除这些相关记录(通常是自动的)的尝试将导致错误。


11
这就是不使用该特定ORM的原因。任何缺乏数据库支持的工具都不值得信赖。外键和级联的删除或更新是db基础知识,不是高级概念,在没有外键约束的情况下,不应设计任何有形数据库!
HLGEM 2011年

问题是他们抛出错误。是否可以限制删除,但引擎不产生错误但仍保持语义?我希望我的程序继续运行,同时保护其他数据不被删除。
TheRealChx101 '19

2

您需要在应用程序的上下文中考虑这一点。通常,您应该设计一个应用程序,而不是数据库(该数据库只是应用程序的一部分)。

考虑您的应用程序应如何应对各种情况。

默认操作是限制(即不允许)该操作,这通常是您想要的操作,因为它可以防止愚蠢的编程错误。但是,在DELETE CASCADE上也可能有用。这实际上取决于您的应用程序以及打算如何删除特定对象。

就个人而言,我会使用InnoDB,因为它不会破坏您的数据(请参阅MyISAM,它会这样做),而不是因为它具有FK约束。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.