具有软删除、唯一键和外键约束的 MySQL
Posted
技术标签:
【中文标题】具有软删除、唯一键和外键约束的 MySQL【英文标题】:MySQL with Soft-Deletion, Unique Key and Foreign Key Constraints 【发布时间】:2011-03-30 09:08:01 【问题描述】:假设我有两张桌子,user
和 comment
。它们的表定义如下所示:
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_id
、table_name
、payload
(以及可选的主键 `id)。
其中original_id
是已删除记录的原始ID,table_name
是已删除记录的表名(在您的情况下为"user"
),payload
是已删除记录所有列的JSON 字符串化字符串.
我还建议在original_id
列上建立索引,以便以后检索数据。
通过这种方式归档数据。您将拥有这些优势
跟踪历史记录中的所有数据 无论删除记录的表结构如何,都只有一个地方可以归档任何表中的记录 不用担心原表中的唯一索引 不用担心在原始表中检查外部索引here 已经是一个讨论,解释了为什么软删除在实践中不是一个好主意。软删除会在未来引入一些潜在的麻烦,例如计数记录,...
【讨论】:
以上是关于具有软删除、唯一键和外键约束的 MySQL的主要内容,如果未能解决你的问题,请参考以下文章