【问题标题】:Opposite of RESTRICT in MySQL Foreign Key On Delete?删除时 MySQL 外键中 RESTRICT 的对面?
【发布时间】:2013-01-28 21:07:41
【问题描述】:

我正在重新设计一些应用程序安全日志表(例如用户登录时、访问不同文件等),以满足一些不断变化的需求。它们最初是用MyISAM 制作的,但并不经常被访问,切换到InnoDB 并添加一堆外键以确保数据完整性确实会更有益。由于无论如何我都必须重新制作桌子,我认为现在是进行转换的好时机。

在大多数情况下,一切都是简单的外键并且按预期工作。我尝试一些奇怪的东西并遇到问题的唯一部分是 user_ids。这些日志表中的每条记录都与一个 user_id 相关联,我想确保在插入记录时存在给定的 user_id。添加引用用户表的外键解决了这个问题 - 简单的东西。以下是一些简洁、有代表性的表格:

用户表

CREATE TABLE tbl_user (
  id INT(10) NOT NULL AUTO_INCREMENT,
  first_name VARCHAR(50),
  PRIMARY KEY(id)
) ENGINE=InnoDB; 

示例日志表

CREATE TABLE tbl_login_time (
  id INT(10) NOT NULL AUTO_INCREMENT,
  user_id INT(10) NOT NULL,
  login_at TIMESTAMP NOT NULL,
  PRIMARY KEY(id),
  CONSTRAINT 'tbl_login_time_fk_1` FOREIGN KEY (user_id) REFERENCES tbl_user
      ON UPDATE CASCADE ON DELETE ???
) ENGINE=InnoDB; 

我的问题是我希望为插入强制执行外键,级联更新,但删除 tbl_user 中的记录根本不影响 tbl_login_time。通常用户会被标记为不活动,但每隔一段时间就会有一个用户被完全删除,但需要维护日志。

MySQL docs 列出了 ON DELETE 的 6 个选项,但听起来都不合适:

  1. RESTRICT:会阻止 tbl_user 中的删除。
  2. NO ACTION:像 RESTRICT 一样进行评估。
  3. CASCADE:会按照我的意愿在 tbl_user 中删除,但也会在 tbl_login_time 中删除。
  4. SET NULL:将在 tbl_user 中删除,并在 tbl_login_time 中保留该行但将数据清空。关闭但没有雪茄。
  5. SET DEFAULT:MySQL 识别它,但拒绝它。
  6. Omit ON DELETE:相当于 RESTRICT。

我以前从未使用过这样的外键(强制执行INSERTUPDATE,但不是DELETE),在阅读了很多其他问题之后,似乎其他人也没有这样做。那可能应该告诉我这是错误的方法,但它可以以某种方式工作吗?

【问题讨论】:

  • 你能用触发器吗?
  • @njk 如果需要,我可以将触发器添加到用户表,但通常我会尽可能避免使用它们。如果事实证明您无法使用外键进行这项工作并且必须使用触发器,那将是一个可以接受的答案。
  • 删除用户记录,同时留下带有 ID 引用的日志似乎不太实用。对我来说似乎是一个设计缺陷。如果您需要保留日志,为什么不使用一些停用标志/摆脱用户日志依赖?
  • @rosipov 经过一番研究并在这里看到了前两个 cmets,我认为这可能不是正确的方法。使用触发器确保只输入有效数据可能是可行的方法,但我仍然很好奇你能用外键完成这个吗?
  • 外键管理依赖关系(A 必须依赖于 B),您试图忽略这一事实(A 必须依赖于 B,除非没有 B)。我将以这种方式使用外键的技术方面留给专家。但是:我真的不喜欢在日志表中没有用户的用户 ID 的想法——一旦用户被删除,它就会变成垃圾数据。底线 - 您很少希望完全删除生产应用程序中的数据。

标签: mysql database-design foreign-keys


【解决方案1】:

我的问题是我希望为插入强制执行外键, 更新被级联,但删除 tbl_user 中的记录不影响 tbl_login_time。

您无法使用外键约束来实现这一点。

在某些应用程序中,ON DELETE SET NULL 是有意义的。但是您的应用程序本质上是一个存储在 SQL 数据库中的日志文件。您有一个重大问题,您想删除识别信息(用户),但在某些情况下保留他们的 ID 号。坦率地说,我不明白你为什么愿意保留用户 23332 于今天 18:40 登录的事实,而不关心你是否能识别用户 23332 是谁

你有几个选择。

  • 删除日志文件表,并将日志文件数据存储在文件系统中的文件中,而不是数据库中。如果我站在你的立场上,我会先考虑这个。如果我们谈论的是可以通过 Web 以某种方式访问​​的数据库,请确保日志文件存储在 Web 根目录之外。 (我会将它与所有其他日志文件一起存储在 /var/log 下。)
  • 使用外键约束,从不删除用户。
  • 使用外键约束,并使用 ON DELETE SET NULL 或 ON DELETE SET DEFAULT 的效果。在这个特定的应用程序中,ON DELETE SET NULL 和 ON DELETE SET DEFAULT 在语义上是等价的。两者都用无法识别用户的数据替换好的数据。如果您仍然无法识别用户 23332,谁在乎您是否知道她在今天 18:40 登录?
  • 删除外键约束,使用触发器做任何你喜欢的事情。

我很确定我们同意最明显的选择——使用带有 ON DELETE CASCADE 的外键——对您的应用程序来说是完全错误的。

【讨论】:

  • You can't accomplish that with a foreign key constraint. - 明白了。你已经说服我你是对的,我试图做的事情没有意义。我开始这样倾斜,这就是我提出问题的原因,但这是很好的反馈/建议,谢谢。
  • 想象一个帐单表,其中包含所有财务记录。它有两个参考:1)对改变平衡的用户; 2)。到客户账户;即使我删除了经理的用户帐户或客户帐户,我也想保留所有这些记录。并且在这里使用日志文件不是一个选项......
  • @pilat:细化您的要求。在英语中,“删除行但保留所有行”的要求实际上是无稽之谈。 (英语不是您的第一语言,对吗?)如果您真的不想删除行,那么您需要一种方法将它们标记为非活动,或将它们标记为“已删除”(实际上没有删除它们),或归档它们等。
  • @MikeSherrill'CatRecall' 精炼:我想保留账单记录,即使 user_id(链接到创建发票的经理)或 client_id 不再存在于参考表。因为它是钱迹,所以我们保留在“帐单”表中,它应该保留。 SET NULL 是其中一种选择,但在剩余 ID 的情况下,我至少可以看到什么是同一个用户/客户端,什么不是。但是,也许,我确实应该采用SET NULL + 一些非规范化的路径(例如,将用户/客户端名称直接写入计费)
  • @pilat:对于金钱追踪,你必须考虑如果你被带到法庭会发生什么。 (以美国为中心的评论和警告。)如果您被送上法庭,您今天打印的作为证据提交的账单必须与您 5 年前邮寄的账单基本相同。将用户/客户(和产品?)名称写入账单。
猜你喜欢
  • 2012-01-10
  • 2015-06-16
  • 1970-01-01
  • 2015-02-22
  • 1970-01-01
  • 1970-01-01
  • 2014-08-03
  • 2017-03-06
  • 2020-06-04
相关资源
最近更新 更多