数据库记录的物理删除与逻辑删除(硬删除与软删除)有何区别?

154

相较于实际或物理删除记录,逻辑/软删除记录的优点是什么(即设置标志表示记录已删除)?

这种做法常见吗?

这样做安全吗?


41
使用删除时间戳,而不是标记。 - Dave Jarvis
4
一面旗帜并不提供任何关于行被删除的时间信息。时间信息有很多用途,包括系统调试。 - Dave Jarvis
3
软删除不会添加有用的审计数据。如果您想创建审计历史记录,请创建一个专注于此目的的次要表。它甚至可以存储先前的版本,并且不会使开发和报告变得非常繁琐。 - Matthew Whited
在实施硬删除之前,请考虑是否删除了仍然需要访问的数据。例如,在博客网站上,从用户表中物理删除用户可能还会删除显示该用户博客条目所需的数据。禁用用户帐户是足够的,并且类似于软删除。 - Dave F
@DaveF,现在你必须非常小心地思考这个问题。由于GDPR和其他各个领域的立法,仅仅禁用用户账户是不够的,如果他们要求删除。你可以选择匿名化而不是删除,但即使如此也有一些限制。 - Luke
26个回答

3
我强烈反对逻辑删除,因为它容易出现许多错误。
首先,在查询时,每个查询必须考虑IsDeleted字段,复杂查询的可能性会增加,从而引发错误。
其次是性能问题:想象一下有一个包含100,000条记录但只有3条有效记录的表,现在将这个数字乘以你数据库中的其他表;另一个性能问题是新记录与旧(已删除的记录)之间可能会发生冲突。
我唯一看到的好处就是记录的历史记录,但还有其他方法可以实现此结果,例如您可以创建一个日志表,其中可以保存信息:TableName,OldValues,NewValues,Date,User [..],其中*Values ​​可以是varchar类型,并以以下形式编写详细信息fieldname : value; [..]或将信息存储为xml
所有这些都可以通过代码或触发器实现,但只能在一个表中查看所有历史记录。 另一种选择是查看指定的数据库引擎是否支持跟踪更改,例如在SQL Server数据库上,有SQL Track Data Change。

1
不错的观点,但是通过在其上添加部分索引来处理性能是可行的。 - lucastamoios

3

如果逻辑删除对参照完整性造成困难,就需要慎重考虑。

当表数据存在时间方面的因素(有效FROM_DATE - TO_DATE)时,逻辑删除是正确的选择。

否则,将数据移动到审计表中并删除记录。

优点是:

这是回滚的简单方式(如果可能的话)。

可以轻松查看特定时间点的状态。


3

除了系统设计之外,还有其他需求需要回答。在记录保留方面,法律或法规要求是什么?根据行与何相关,可能存在法律要求在“暂停”后的一定时间内保留数据。

另一方面,要求可能是一旦记录被“删除”,它就真正且不可撤销地被删除。在做出决定之前,请与您的利益相关者进行交流。


2
移动应用程序依赖同步的情况下,可能需要使用逻辑删除而非物理删除:服务器必须能够向客户端指示记录已被(标记为)删除,如果记录被物理删除,则可能无法实现此功能。请保留HTML标签。

2

在需要保留历史记录(例如用户账户,如@Jon Dewees所提到的)或者存在用户要求撤销删除操作的情况下,这是相当标准的做法。

如果你担心从查询中过滤掉已删除记录的逻辑会变得混乱并使查询变得复杂,那么你可以建立视图来为你进行过滤,并对其进行查询。这将防止这些记录泄露在报告解决方案等中。


2

我想对提到的唯一约束问题进行进一步解释。

假设我有一个表,包含两列:idmy_column。为支持软删除,我需要更新表定义如下:

create table mytable (
  id serial primary key,
  my_column varchar unique not null,
  deleted_at datetime
)

但是,如果一行已被软删除,我希望忽略my_column的约束条件,因为已删除的数据不应干扰未删除的数据。我的原始模型将无法工作。

我需要将我的数据定义更新为以下内容:

create table mytable (
  id serial primary key,
  my_column varchar not null,
  my_column_repetitions integer not null default 0,
  deleted_at datetime,
  unique (my_column, my_column_repetitions),
  check (deleted_at is not null and my_column_repetitions > 0 or deleted_at is null and my_column_repetitions = 0)
)

并应用此逻辑:当一行是当前行时,即未删除时,my_column_repetitions 应该保持默认值 0,当行被软删除时,其 my_column_repetitions 需要更新为 (软删除行上最大的重复次数) + 1。

后者的逻辑必须通过触发器或在我的应用程序代码中处理程序来实现,并且没有可以设置的检查。

对于每个唯一的列都要重复这个过程!

我认为这种解决方案真的很杂乱,而且更倾向于使用一个单独的 归档 表来存储已删除的行。


1
为了回复Tohid的评论,我们遇到了同样的问题,希望保留记录历史,并且我们不确定是否需要'is_deleted'列。
我在谈论我们的Python实现和类似的用例。
我们发现https://github.com/kvesteri/sqlalchemy-continuum这是一个简单的获取对应表的版本表的方法。最少的代码行并捕获添加、删除和更新的历史。
这比只有'is_deleted'列更有用。您可以始终向后引用版本表以检查此条目发生了什么。是否已删除、已更新或已添加。
这样,我们根本不需要'is_deleted'列,我们的删除函数也非常简单。这样,我们也不需要记住在任何api中标记'is_deleted=False'。

1

他们不允许数据库按照应有的方式执行,导致级联功能等诸如此类的东西无用。

对于简单操作例如插入,在重新插入时,其后面的代码就会翻倍。

您不能仅仅简单地插入,而是必须检查是否存在,如果不存在则插入,否则更新删除标志,并将所有其他列更新为新值。这被视为对数据库事务日志的更新,而不是全新的插入,从而导致不准确的审计日志。

它们会导致性能问题,因为表格中存在冗余数据。这会对索引造成严重影响,特别是唯一性方面。

我不是逻辑删除的粉丝。


0

好吧!就像大家所说的,这取决于情况。

如果您在像UserName或EmailID这样的列上有一个索引 - 而且您永远不希望再次使用相同的UserName或EmailID,则可以选择软删除。

话虽如此,始终检查您的SELECT操作是否使用主键。如果您的SELECT语句使用主键,则添加带有WHERE子句的标志不会有太大的区别。让我们以一个示例为例(伪代码):

表Users(UserID [主键],EmailID,IsDeleted)

SELECT * FROM Users where UserID = 123456 and IsDeleted = 0

这个查询在性能上不会有任何影响,因为UserID列具有主键。最初,它将基于PK扫描表,然后执行下一个条件。

软删除无法完全起作用的情况:

在大多数网站上注册都使用EmailID作为唯一标识。我们非常清楚,一旦在像Facebook、G+这样的网站上使用了一个EmailID,其他人就不能再使用它。

当用户想从网站中删除他/她的个人资料时,就会出现这样的情况。现在,如果您进行逻辑删除,该用户将永远无法再次注册。此外,再次使用相同的EmailID注册并不意味着恢复整个历史记录。每个人都知道,删除意味着删除。在这种情况下,我们必须进行物理删除。但是,为了维护帐户的整个历史记录,我们应该始终将这些记录存档在归档表或已删除表中。

是的,在存在大量外部表的情况下,处理可能非常繁琐。

还要记住,软/逻辑删除会增加您的表大小和索引大小。


0

我已经在另一篇帖子中回答过了。 不过,我认为我的回答更适合这里的问题。

我的软删除实际解决方案是通过创建一个新表来进行归档,该表具有以下列:original_idtable_namepayload(以及可选的主键id)。
其中,original_id 是已删除记录的原始 ID,table_name 是已删除记录的表名(在您的情况下为"user"),payload 是已删除记录所有列的 JSON 字符串化字符串。
我还建议在列 original_id 上建立索引,以便以后检索数据。
通过这种方式归档数据,您将获得以下优势:
  • 跟踪历史上的所有数据
  • 只需一个地方即可归档来自任何表的记录,而不管已删除记录的表结构如何
  • 无需担心原始表中的唯一索引
  • 无需担心检查原始表中的外部索引
  • 不再需要在每个查询中使用WHERE子句来检查删除
这里已经有一个讨论here,解释了为什么软删除在实践中不是一个好主意。软删除会在未来引入一些潜在的麻烦,例如计算记录数等。

我已经写了一篇关于所有数据删除方式的博客文章。https://transang.me/database-design-practice-soft-deletion-to/ - transang

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接