软删除最佳实践(PHP/MySQL)

31
问题

在处理产品和订单的Web应用程序中,我想维护以前雇员(用户)处理的订单的信息和关系。我还想维护包括这些产品的订单和过时产品之间的信息和关系。

然而,我想让员工能够简化管理界面,例如删除以前的员工、过时的产品、过时的产品组等。

我考虑实现软删除。那么,人们通常如何做到这一点呢?

我的立即想法

我的第一个想法是在每个可软删除的对象表中添加一个 "flag_softdeleted TINYINT NOT NULL DEFAULT 0" 列。或者也可以使用时间戳?

然后,在每个相关的GUI中提供一个 "显示已删除" 或 "取消删除" 按钮。点击此按钮将在结果中包含软删除记录。每个删除的记录都有一个 "还原" 按钮。这样做有意义吗?

你有什么想法吗?

另外,如果您有任何相关资源的链接,我将不胜感激。


根据您的员工/产品表的大小,创建一个包含软删除项目ID的单独表可能更好。 - drudge
时间戳总是有用的。但是你可以使用任何你想要的 - 这并不是一个很大的问题。 - Your Common Sense
7个回答

39
这就是我的做法。我有一个默认值为0的is_deleted字段。然后查询只需检查WHERE is_deleted = 0
我尽可能避免使用硬删除。有时候是必要的,但我将其作为仅限管理员的功能。这样我们就可以进行硬删除,但用户不能...
编辑:实际上,您可以使用此方法在应用程序中拥有多个“层次”软删除。因此,每个都可以是一个代码:
  • 0->未删除
  • 1->软删除,在被管理用户的已删除项目列表中显示
  • 2->软删除,除了管理员用户外,其他任何用户都不会显示出来
  • 3->仅对开发人员可见。
拥有其他两个级别仍将允许管理员和管理人员清理已删除列表,如果它们变得太长。由于前端代码只检查is_deleted = 0,因此对前端透明...

1
我们为内部CRM使用分层层次结构。我们还使用触发器将已删除的条目添加到备份表中。如果有人需要审计管理员级别帐户或管理员级别用户意外删除了错误记录,这将非常方便。 - Jestep
3
枚举类型可能比0、1、2、4更好。枚举类型可以自我说明,并且具有相同的性能表现。 - user479911
注意不要被唯一列打败:您的“已删除”元素仍将在唯一列中保留一个值,这可能不是您想要的。但是,在软删除原始数据时,您可以将此列设置为NULL以避免冲突... - Julien Palard
如果普通用户软删除具有子记录的父记录,会发生什么情况?在这种情况下,父记录及其所有子记录将对普通用户隐藏。这与级联删除问题完全相同。因此,我的问题是,在软删除父记录之前如何检查引用完整性? - manas
1
你可以使用一个额外的字段(或替换此字段)作为日期时间/时间戳,以了解行何时被“删除”。 - JorgeGarza

11

使用软删除是一个常见的实现方法,它们对许多事情非常有用,比如:

  • 在用户删除某些内容时保存其数据
  • 在您删除某些内容时保存自己的数据
  • 保留发生的记录(一种审计)
  • 等等

我想要指出的一件事是几乎所有人都会忽略的,它总会回来咬你一口。您的应用程序的用户对删除的理解与您不同。

存在不同的删除程度。典型的用户在以下情况下删除内容:

  • 犯了错误并想删除错误数据
  • 不再希望在屏幕上看到某些内容

问题在于,如果您不记录删除的意图,则您的应用程序无法区分错误数据(本不该创建的数据)和历史上正确的数据。

请看以下数据:

PRICES | item | price | deleted |
       +------+-------+---------+
       |   A  |  101  |    1    |
       |   B  |  110  |    1    |
       |   C  |  120  |    0    |
       +------+-------+---------+
有些用户不想展示物品 B 的价格,因为他们已经不再销售该物品。所以他删除了它。另一个用户误创建了物品 A 的价格,然后删除了它并创建了物品 C 的价格,如他所意图的那样。现在,你能给我显示所有产品价格的清单吗?不行,因为要么你必须显示可能错误的数据(A),要么你必须排除除当前价格以外的所有价格(C)。
当然,以上情况可以通过多种方式处理。我的观点是需要非常清楚地表达对删除的含义,并确保用户无法误解它。一种方法是强制用户进行选择(隐藏/删除)。

6
如果我有现有代码涉及到该表,我会添加列并更改表名。然后我会创建一个与当前表同名的视图,仅选择活动记录。这样就不会影响任何现有代码,并且可以拥有软删除列。如果您想查看已删除的记录,则从基本表中进行选择,否则使用视图即可。

虽然简短,但我愿意接受这个解决方案。原则上,在实际生产数据库中也适用,绝对是正确的做法。没有人希望删除一列并且容易忘记在应用程序中使用的众多查询之一中添加条件“where deleted=0”。 - MohamedEzz

1

我一直都是像你提到的那样,使用一个删除列。实际上,这并不需要太多的操作。与其删除记录,只需将删除字段设置为true即可。

我构建的某些组件允许用户查看所有已删除的记录并恢复它们,而其他组件则仅显示删除=0的所有记录。


1

你的想法确实有道理,并且在生产中经常使用,但是要实施它,您需要更新相当多的代码以考虑新字段。另一个选项可能是将“软删除”的记录存档(移动)到单独的表或数据库中。这也经常这样做,使问题成为维护而不是重新编程的问题。(您可以使表触发器响应删除以存档已删除的记录。)

我会进行存档以避免对生产代码进行重大更新。但是,如果您想使用已删除标志字段,请将其用作时间戳,以为您提供除布尔值之外的其他有用信息。(Null = 未删除。)您还可能希望添加一个DeletedBy字段来跟踪负责删除记录的用户。使用两个字段为您提供了很多信息,告诉您谁在何时删除了什么。(在存档表/数据库中完成两个额外字段的解决方案也是一种方法。)


0
我遇到的最常见情况是您所描述的,即使用tinyint或甚至bit表示IsActiveIsDeleted状态。根据这是否被视为“业务”或“持久性”数据,它可能会尽可能透明地嵌入应用程序/域逻辑中,例如直接存储过程中而不为应用程序代码所知。但是,听起来这是您需要的合法业务信息,因此需要在整个代码中得到知晓。(正如您建议的那样,用户可以查看已删除的记录。)
我见过的另一种方法是使用两个时间戳的组合来显示给定记录的“活动窗口”。维护它需要更多的代码,但好处是可以预定时间软删除某些内容。例如,可以在创建有限时间产品时设置该方式。(要使记录无限期活动,可以使用最大值(或仅使用某个极远的未来日期),或者如果您愿意,只需将结束日期设为null。)
当然,还需要考虑到有时会删除/恢复某些内容,并跟踪某种审计。标志方法只知道当前状态,时间戳方法只知道最近的窗口。但是像审计跟踪这样复杂的内容一定应该单独存储,而不是与相关记录混在一起。

MySQL中有BIT类型?我一直使用TINYINT。惊呆了在布尔字段中使用BIT和TINYINT有什么区别? - user479911
@arex1337:“bit” 实际上曾经是 MySQL 中 “tinyint” 的同义词: http://dev.mysql.com/doc/refman/5.0/en/numeric-type-overview.html 你最好使用 tinyint ,但是不要引用我说的话。在一些其他数据库中,一个主要的区别是 bit 无法被索引,或者处理 null 不同(或根本不处理),等等。但是我对 MySQL 的数据类型没有足够的直接了解,不能成为权威。 - David

0

相反,我会使用一个bin表来移动从其他表中删除的所有记录。使用删除标志的主要问题是,在使用链接表时,插入新记录时肯定会遇到双键错误。

bin表的结构可以像这样:

id, table_name, data, date_time, user

在哪里

  • id 是自增的主键
  • table_name 是被删除记录所在表的名称
  • data 包含所有字段的名称和值,以 JSON 格式存储
  • date_time 是删除操作的日期和时间
  • user 是执行该操作的用户标识符(如果系统提供)

这种方法不仅可以避免在每个查询中检查删除标志(想象一下有许多连接的查询),而且还可以使表中只保留真正必要的数据,从而方便使用 SQL 客户端程序进行任何搜索和更正。


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