具有软删除、唯一键和外键约束的 MySQL

Posted

技术标签:

【中文标题】具有软删除、唯一键和外键约束的 MySQL【英文标题】:MySQL with Soft-Deletion, Unique Key and Foreign Key Constraints 【发布时间】:2011-03-30 09:08:01 【问题描述】:

假设我有两张桌子,usercomment。它们的表定义如下所示:

CREATE TABLE `user` (
  `id`       INTEGER NOT NULL AUTO_INCREMENT,
  `username` VARCHAR(255) NOT NULL,
  `deleted`  TINYINT(1) NOT NULL DEFAULT 0,
  PRIMARY KEY (`id`),
  UNIQUE KEY (`username`)
) ENGINE=InnoDB;
CREATE TABLE `comment` (
  `id`      INTEGER NOT NULL AUTO_INCREMENT,
  `user_id` INTEGER NOT NULL,
  `comment` TEXT,
  `deleted` TINYINT(1) NOT NULL DEFAULT 0,
  PRIMARY KEY (`id`),
  CONSTRAINT `fk_comment_user_id` FOREIGN KEY (`user_id`)
    REFERENCES `user` (`id`)
    ON DELETE CASCADE
    ON UPDATE CASCADE
) ENGINE=InnoDB;

这对于强制执行数据完整性等非常有用,但我希望能够“删除”用户并保留其所有 cmets(仅供参考)。

为此,我添加了deleted,以便我可以在记录中使用SET deleted = 1。通过默认使用deleted = 0 列出所有内容,我可以隐藏所有已删除的记录,直到我需要它们。

到目前为止一切顺利。

问题来了:

用户使用用户名(例如“Sam”)注册, 我软删除了该用户(出于不相关的原因),并且 其他人以 Sam 的身份注册,突然我们违反了 user 的 UNIQUE 约束。

我希望用户能够编辑自己的用户名,所以我不应该将username作为主键,删除用户时我们仍然会遇到同样的问题。

有什么想法吗?

编辑澄清:在下面添加了 RedFilter 的答案和 cmets。

我担心“已删除”的用户和 cmets 对公众不可见,但只有管理员可见,或者为了计算统计数据而保留。

这个问题是一个思想实验,用户和评论表只是示例。尽管如此,username 并不是最好用的。 RedFilter 对用户身份提出了有效的观点,尤其是当记录在公共环境中呈现时。

关于“为什么用户名不是主键?”:这只是一个示例,但如果我将其应用于实际问题,我将需要在假设存在代理主键的现有系统。

【问题讨论】:

【参考方案1】:

在字段上添加唯一约束(用户名,已删除) 将“已删除”的字段类型更改为 INTEGER。

在删除操作期间(可以在触发器中完成,或者在您需要实际删除用户的部分代码中)将 id 字段的值复制到已删除的字段。

这种方法允许您:

为活跃用户保留唯一名称(已删除 = 0) 允许多次删除具有相同用户名的用户

“已删除”字段不能只有 2 个值,因为以下情况将不起作用:

    您创建了用户“Sam” 用户 Sam 已被删除 您使用用户名“Sam”创建新用户 您尝试删除用户名为“Sam”的用户 - 失败。您已经有记录 userName = 'Sam' and deleted = '1'

【讨论】:

deleted 的字段类型更改为timestamp 将允许您在那里添加更多已删除的行。如果您想要现有(未删除)记录,请在此字段中检查 null @TahaPaksu 如果您允许删除空值,您将丢失唯一键的值。 @eric_s 这种方法需要设计为“除 1 之外的所有内容都将被视为'未删除'”。 至少在 PostgreSQL 中有一种方法可以协调这两个要求 - 使用部分索引,参见。 this comment. 这个想法可以在用户名和deletedAt上使用一对密钥来实现【参考方案2】:

只需保留 username 上的唯一索引或约束。您不希望新用户能够使用已删除的名称,因为不仅可能会导致身份混淆,而且如果您仍然显示已删除用户的旧帖子,那么它们将被错误地理解为由同名的新用户。

当新用户注册时,您通常会在允许注册完成之前检查该名称是否正在使用,因此这里应该没有冲突。

.

【讨论】:

@RedFilter,它不是一个完整的解决方案 - 您将无法将两个“Sam”标记为已删除。在删除操作期间最好将 Id 复制到已删除的字段。这允许只保留一个人处于活动状态,并有几个同名的已删除用户(通过使用 const unique(name, deleted) )。 @Michael:我不同意两个不同的用户应该能够使用相同的用户名。 @Michael:第一反应,“那是个黑客”。第二反应,“哇,这实际上很聪明”。大概您的意思是将约束设置为UNIQUE KEY (username, deleted) @Rob Howard,谢谢。准备好正确的答案。 @Rob:我认为从可用性和社区的角度来看,允许多个非并发用户使用相同的用户名是一个坏主意,即使您提供的新信息是旧用户的信息将被隐藏。这不会将它们从其他用户的记忆中抹去,而且可能会出现混乱。【参考方案3】:

我的软删除实用解决方案是通过创建一个包含以下列的新表来归档:original_idtable_namepayload(以及可选的主键 `id)。

其中original_id 是已删除记录的原始ID,table_name 是已删除记录的表名(在您的情况下为"user"),payload 是已删除记录所有列的JSON 字符串化字符串.

我还建议在original_id 列上建立索引,以便以后检索数据。

通过这种方式归档数据。您将拥有这些优势

跟踪历史记录中的所有数据 无论删除记录的表结构如何,都只有一个地方可以归档任何表中的记录 不用担心原表中的唯一索引 不用担心在原始表中检查外部索引

here 已经是一个讨论,解释了为什么软删除在实践中不是一个好主意。软删除会在未来引入一些潜在的麻烦,例如计数记录,...

【讨论】:

以上是关于具有软删除、唯一键和外键约束的 MySQL的主要内容,如果未能解决你的问题,请参考以下文章

3.啥是键、候选键、主键和外键?

主键约束,外键约束,空值约束,默认值约束,唯一约束,检查约束的各个作用是啥?

在mysql中怎么同时运用主键和外键约束

oracle中外键的作用

SQL Server 中的“键”是啥意思?主键和外键是啥?

数据库-主键和外键及其约束