数据库记录的物理删除还是逻辑删除(硬删除还是软删除) ?

与实际或物理删除记录相比,对记录进行逻辑/软删除(即设置一个标志,表明该记录已被删除)的好处是什么?

这是常见的做法吗?

这里安全吗?

92866 次浏览

这样做的好处是可以保留历史记录(有利于审核) ,而且不必担心通过数据库中引用正在删除的行的其他各种表级联执行删除操作。缺点是您必须编写任何报告/显示方法来考虑标志。

至于它是否是一种常见的实践——我会说是的,但是就任何事情而言,您是否使用它取决于您的业务需求。

编辑: 想到另一个缺点-如果你有唯一的索引表,被删除的记录仍然会占用“一个”记录,所以你必须围绕这种可能性编写代码(例如,一个用户表,有一个唯一的索引用户名; 一个被删除的记录仍然会阻止被删除的用户用户名的新记录。解决这个问题,你可以在已删除的用户名列上添加一个 GUID,但是这是一个我不推荐的非常蹩脚的解决方案。也许在这种情况下,最好只有一个规则,即一旦使用了用户名,就永远不能被替换。)

我通常使用逻辑删除-我发现它们工作得很好,当你也间歇性地将“删除”的数据归档到一个归档表(如果需要可以搜索) ,因此没有机会影响应用程序的性能。

它工作得很好,因为如果您曾经审计过数据,您仍然拥有它。如果您物理删除它,不见了

我非常喜欢逻辑删除,特别是对于业务线应用程序,或者在用户帐户的上下文中。我的理由很简单: 通常情况下,我不希望一个用户能够使用该系统了(所以该帐户被标记为删除) ,但如果我们删除用户,我们会失去他们所有的工作等。

另一个常见的场景是,用户可能在被删除后重新创建一段时间。对于用户来说,将所有数据显示在删除之前,而不是重新创建数据,这是一种更好的体验。

我通常认为删除用户更多的是“暂停”他们无限期。你永远不知道他们什么时候会合法地需要回来。

逻辑删除如果对参照完整性有困难。

当表数据具有时间方面(有效的 FROM _ DATE-TO _ DATE)时,这是正确的想法。

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

好的一面是:

这是更容易的回滚方式(如果可能的话)。

很容易看到在特定时间点的状态。

回复: “这安全吗?”——这取决于你的意思。

如果你的意思是通过物理删除,你将 阻止任何人找到被删除的数据,然后是的,这或多或少是正确的; 您更安全的物理删除敏感数据,需要被删除,因为这意味着它永久地从数据库。(然而,要意识到这些数据可能还有其他副本,比如备份,或者交易日志,或者传输中的记录版本,比如数据包嗅探器——仅仅因为你从数据库中删除了数据,并不能保证它没有被保存在其他地方

如果您的意思是通过执行逻辑删除操作,那么您的数据更加安全,因为 你永远不会丢失任何数据也是如此。这对审计场景很有用; 我倾向于这样设计,因为它承认一个基本事实,即一旦数据生成,它永远不会离开 真的(特别是如果它曾经有能力,比如说,被互联网搜索引擎缓存)。当然,真正的审计场景不仅要求删除是合乎逻辑的,而且还要求记录更新,以及更改的时间和进行更改的参与者。

如果您的意思是数据不会落入不应该看到它的任何人的手中,那么这完全取决于您的应用程序及其安全结构。在这方面,逻辑删除与数据库中的其他任何内容相比,都不是更安全或更不安全的。

当你想要保存某些东西的历史记录时,它是相当标准的(例如@Jon Dewees 提到的用户帐户)。如果用户有很大的机会要求取消删除,这当然是一个很好的主意。

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

逻辑删除是常见的做法吗?是的,我在很多地方都看到过。他们安全吗?这真的取决于他们是否比你删除之前的数据更不安全?

当我还是一个技术领导的时候,我要求我们的团队保留所有的数据,我当时知道我们将会使用所有的数据来构建各种 BI 应用程序,尽管当时我们不知道需求是什么。虽然从审计、故障排除和报告的角度来看,这是好的(这是一个用于 B2B 交易的电子商务/工具网站,如果有人使用了一个工具,我们希望记录下来,即使他们的帐户后来被关闭) ,它确实有几个缺点。

缺点包括(不包括已经提到的其他缺点) :

  1. 保存所有数据的性能含义,我们开发各种归档策略。例如,应用程序的一个部分接近于每周生成大约1Gb 的数据。
  2. 保存数据的成本确实会随着时间的推移而增长,尽管磁盘空间很便宜,但保存和管理 TB 级数据的在线和离线基础设施的数量是很大的。冗余需要大量的磁盘,人们需要时间来确保备份的快速移动等等。

当决定使用逻辑删除、物理删除或存档时,我会问自己以下问题:

  1. 可能需要重新插入到表中的数据。例如,用户帐户适合此类别,因为您可能激活或停用用户帐户。如果是这种情况,逻辑删除是最有意义的。
  2. 存储数据有什么内在价值吗?如果是这样,将生成多少数据。根据这一点,我要么使用逻辑删除,要么实现归档策略。请记住,您总是可以存档逻辑上已删除的记录。

他们不让数据库执行,因为它应该呈现一些无用的级联功能。

对于插入这样的简单事情,在重新插入的情况下,它背后的代码会翻倍。

您不能只是简单地插入,相反,您必须检查一个存在,如果它之前不存在,则插入; 如果存在,则更新删除标志,同时将所有其他列更新为新值。这被视为对数据库事务日志的更新,而不是导致不准确审计日志的新插入。

它们会导致性能问题,因为表被冗余数据堵塞了,这对索引特别是唯一性造成了损害。

我不喜欢逻辑删除。

这可能有点晚,但我建议大家检查 Pinal Dave 的博客文章关于逻辑/软删除:

我一点也不喜欢这种设计[软删除]。我坚信这种架构,即只有必要的数据应该放在单个表中,无用的数据应该移动到归档表中。我建议使用两个不同的表,而不是遵循 isDelete 列: 一个带有订单,另一个带有已删除订单。在这种情况下,您必须同时维护这两个表,但是在现实中,它非常容易维护。将 UPDATE 语句写入 isDelete 列时,将 INSERTINTO 写入另一个表并从原始表中将其 DELETE。如果情况是回滚,则按相反的顺序写入另一个 INSERTINTO 和 DELETE。如果您担心事务失败,请将此代码包装在 TRANSACTION 中。

在上述情况下,较小的表与较大的表的优点是什么?

  • 较小的桌子容易维护
  • 索引重建操作要快得多
  • 将归档数据移动到另一个文件组将减少主文件组的负载(考虑到所有文件组都在不同的系统上)——这也将加快备份。
  • 由于规模较小,将经常更新统计数据,这将减少资源密集程度。
  • 索引的大小将会更小
  • 使用更小的表大小可以提高表的性能。

我强烈 不同意与逻辑删除,因为您暴露了许多错误。

首先,每个查询必须注意 IsDelete 字段,复杂查询出错的可能性更高。

其次是性能: 想象一个只有3条活动记录的100000条记录的表,现在将这个数字乘以数据库的表; 另一个性能问题是可能与新记录和旧记录(已删除记录)发生冲突。

我看到的唯一优势是记录的历史,但是还有其他方法可以实现这个结果,例如,您可以创建一个日志表,在这里您可以保存信息: TableName,OldValues,NewValues,Date,User,[..],其中 *Values可以是 varchar,并以这种形式写入详细信息 fieldname : value; [。.]或者将信息存储为 xml

所有这些都可以通过代码或触发器来实现,但是您只是具有所有历史的 表。 另一个选项是查看指定的数据库引擎是否本机支持跟踪更改,例如在 SQLServer 数据库上有 SQL 跟踪数据更改。

软删除是一种编程实践,当数据更相关时,大多数应用程序都会遵循这种实践。考虑一个金融应用程序的案例,最终用户的错误删除可能是致命的。 当软删除变得相关时,就是这种情况。在软删除中,用户实际上并没有从记录中删除数据,而是将其标记为 IsDelete 为 true (按照常规约定)。

在 EF 6.x 或 EF 7之后,Softdelete 被添加为一个属性,但是我们现在必须创建一个自定义属性。

我强烈推荐在数据库设计中使用 SoftDelete,它对于编程实践来说是一个很好的约定。

除了系统设计之外,还有需要回答的需求。记录保留的法律或法定要求是什么?根据行的相关性,可能会有一个法律要求,要求在数据“挂起”后保存一段时间。

另一方面,要求可能是,一旦记录被“删除”,它就是真正的和不可撤销的删除。在你做决定之前,和你的利益相关者谈谈。

我是一个 NoSQL 开发人员,在我上一份工作中,我处理的数据总是对某人至关重要,如果它在创建的同一天被意外删除,我无法在昨天的最后一个备份中找到它!在这种情况下,软删除总能挽救局面。

我使用时间戳进行了软删除,记录了文档被删除的日期:

IsDeleted = 20150310  //yyyyMMdd

每个星期天,都有一个进程在数据库上进行遍历,并检查 IsDeleted字段。如果当前日期和时间戳之间的差异大于 N 天,则很难删除文档。考虑到这份文件在某些备份中仍然可用,这样做是安全的。

编辑: 这个 NoSQL 用例是关于在数据库中创建的大型文档,每天数十个或数百个,但不是数千个或数百万个。一般来说,它们是具有工作流过程的状态、数据和附件的文档。这就是为什么用户有可能删除重要文件的原因。这个用户可以是具有管理员权限的人,也可以是文档的所有者,仅举几个例子。

我的用例不是大数据。在这种情况下,您将需要一种不同的方法。

正如大家所说,这取决于情况。

如果在列上有一个像 UserName 或 EmailID 这样的索引,并且不希望再次使用相同的 UserName 或 EmailID,那么可以使用软删除。

也就是说,始终检查 SELECT 操作是否使用主键。如果您的 SELECT 语句使用主键,则添加带有 WHERE 子句的标志不会产生太大差别。让我们举一个例子(伪) :

表用户(UserID [主键]、 EmailID、 IsDelete)

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

由于 UserID 列有一个主键,因此此查询在性能方面不会产生任何差异。最初,它将基于 PK 扫描表,然后执行下一个条件。

软删除根本无法工作的情况:

注册在大多数网站采取电子邮件 ID 作为您的唯一标识。我们非常清楚,一旦 EmailID 被用在像 facebook,G + 这样的网站上,它就不能被其他任何人使用。

有一天,用户想要从网站上删除他/她的个人资料。现在,如果进行逻辑删除,该用户将无法再次注册。另外,使用相同的 EmailID 再次注册并不意味着恢复整个历史记录。大家都知道,删除就是删除。在这种情况下,我们必须进行物理删除。但是,为了维护帐户的整个历史,我们应该始终将这些记录归档到归档表或删除表中。

是的,在我们有很多外来表的情况下,处理是相当麻烦的。

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

依赖于同步的移动应用程序可能会强制使用逻辑删除而不是物理删除: 服务器必须能够向客户端指示记录已被(标记为)删除,如果记录被物理删除,这可能是不可能的。

我以前用软删除,只是为了保存旧记录。我意识到用户不会像我想象的那样频繁地查看旧记录。如果用户想要查看旧记录,他们只能从存档或审计表中查看,对吗?那么,软删除的好处是什么?它只会导致更复杂的查询语句等等。

以下是我在决定不再使用软删除之前实现的内容:

  1. 实施审计,记录所有活动(添加、编辑、删除)。确保没有外键链接到审计,并确保该表是安全的,除了管理员之外没有人可以删除。

  2. 识别哪些表被认为是“事务性表”,这些表很可能被保留很长时间,并且很可能用户想要查看过去的记录或报告。例如,购买交易。这个表不仅应该保留主表的 id (比如 dept-id) ,还应该保留其他信息,比如名称作为参考(比如 dept-name) ,或者其他报告所需的字段。

  3. 实现主表的“活动/非活动”或“启用/禁用”或“隐藏/显示”记录。因此,用户可以禁用/不激活主记录,而不是删除记录。这样更安全。

只是我的个人意见。

我使用的一种模式是创建一个镜像表并在主表上附加一个触发器,因此所有的删除(如果需要,还有更新)都记录在镜像表中。

这允许你“重建”已删除或已更改的记录,你仍然可以在主表中硬删除并保持其“干净”——它还允许创建一个“撤销”函数,你还可以记录在镜像表中执行操作的日期、时间和用户(在政治迫害的情况下是无价的)。

另一个好处是在查询主服务器时不会意外地包含已删除的记录,除非您故意包含镜像表中的记录(您可能希望显示实时记录和已删除记录)。

另一个优点是镜像表可以独立地进行清除,因为它不应该有任何实际的外键引用,与从使用软删除但仍然与其他表有引用连接的主表进行清除相比,这是一个相对简单的操作。

还有什么好处?很好,如果你有一群程序员在项目中工作,以混合的技能和对细节水平的关注读取数据库,你不必熬夜希望他们中的一个没有忘记包括删除记录(lol,不包括已删除记录 = 真) ,这会导致夸大客户可用的现金头寸,然后他们去买一些股票(例如,在交易系统中) ,当你与交易系统一起工作时,你会很快发现健壮的解决方案的价值,即使他们可能有一点点更初始的“开销”。

例外:
- 以“软删除”作为指引,对“引用”数据(如用户、类别等)使用“软删除”,对“事实”类型的数据(即交易历史)使用“硬删除”镜像表。

为了回应托希德的评论,我们面临着同样的问题,我们想要保持记录的历史,也不确定我们是否想要 is_deleted专栏。

我说的是我们的 python 实现和我们遇到的一个类似的用例。

我们遇到了 https://github.com/kvesteri/sqlalchemy-continuum,这是一个简单的方法来获得版本表对应的表。最少的代码行,并捕获添加、删除和更新的历史记录。

这不仅仅服务于 is_deleted列。您总是可以反向引用版本表来检查这个条目发生了什么。条目是否被删除、更新或添加。

这样我们根本不需要 is_deleted列,并且我们的 delete 函数非常简单。这样我们也不需要记住在任何 api 中标记 is_deleted=False

大多数时候使用软删除是因为你不想暴露一些数据,但是由于历史原因你必须保留它(一个产品可能会停产,所以你不想与它进行任何新的交易,但是你仍然需要处理销售交易的历史)。顺便说一下,有些人在销售交易数据中复制产品信息值,而不是引用产品来处理这个问题。

事实上,它看起来更像是对可见/隐藏或活动/非活动特性的重新措辞。因为这就是商业世界里“删除”的意思。我想说的是,终结者可以删除人,但老板只是解雇他们。

这种做法是非常常见的模式,许多应用程序使用它有很多原因。因为这不是实现这个目标的唯一方法,所以你会有成千上万的人说这是伟大的或者胡说八道的,而且两者都有很好的论据。

从安全性的角度来看,SoftDelete 不会取代审计作业,也不会取代备份作业。如果您害怕“在两个备份用例之间插入/删除”,您应该阅读关于 Full 或 Bulk 恢复模型。我承认 SoftDelete 可以使恢复过程更加琐碎。

由你决定你的要求。

为了提供另一种选择,我们让用户使用通过 MobiLink 更新的远程设备。如果我们删除服务器数据库中的记录,那么这些记录永远不会在客户端数据库中被标记为删除。

所以我们两个都做。我们与我们的客户一起工作,以确定他们希望能够恢复数据多长时间。例如,一般来说,客户和产品是活跃的,直到我们的客户说他们应该被删除,但是销售历史记录只保留13个月,然后自动删除。客户可能希望将删除的客户和产品保留两个月,但将历史记录保留六个月。

所以我们连夜运行一个脚本,根据这些参数在逻辑上标记删除的内容,然后两个月或六个月后,今天标记为逻辑删除的内容将很难被删除。

与其说我们关心的是数据安全,不如说我们关心的是在内存有限的客户端设备(如智能手机)上拥有庞大的数据库。一个客户如果连续四年每周两次订购200种产品,那么他将拥有超过81,000条历史线索,其中75% 的客户不在乎他是否看到了。

这完全取决于系统的用例及其数据。

例如,如果你正在谈论一个政府监管的系统(例如,一个制药公司的系统被认为是质量系统的一部分,必须遵循 FDA 的电子记录指南) ,那么你最好不要做硬删除!FDA 的审核员可以过来要求系统中与产品编号 ABC-123有关的所有记录,并且所有数据最好是可用的。如果您的业务流程所有者说,系统不应该允许任何人在未来的新记录中使用产品编号 ABC-123,那么可以使用软删除方法,使其在系统中“不活动”,同时仍然保留历史数据。

然而,也许您的系统及其数据有一个用例,比如“跟踪北极的天气”。也许你每小时测量一次体温,然后在一天结束的时候把每天的平均值加起来。也许每小时的数据在聚合之后将不再使用,并且您将在创建聚合之后难以删除每小时的读数。(这是一个虚构的、琐碎的例子。)

关键是,这完全取决于系统及其数据的用例,而不是纯粹从技术角度做出的决定。

我几乎总是软删除,原因如下:

  • 如果客户要求您恢复已删除的数据,您可以这样做。用软删除让顾客更快乐。从备份中恢复特定数据很复杂
  • checking for isdeleted everywhere is not an issue, you have to check for userid anyway (if the database contains data from multiple users). You can enforce the check by code, by placing those two checks on a separate function (or use views)
  • 优雅删除。处理已删除内容的用户或进程将继续“查看”该内容,直到他们点击下一次刷新。如果一个进程正在处理某些突然删除的数据,这是一个非常理想的特性
  • 同步: 如果你需要设计一个数据库和移动应用程序之间的同步机制,你会发现软删除更容易实现

我已经回答了 在另一个职位。 然而,我认为我的答案更适合这里的问题。

我的软删除实用解决方案是通过创建一个新的 表中包含以下列: original_idtable_namepayload、, (和一个可选的主键‘ id)。

其中 original_id是已删除记录的原始 ID,table_name 是已删除记录的表名(在您的示例中为 "user") , payload是由 JSON 字符串从所有列中删除的 记录。

我还建议为后者在 original_id列上建立一个索引 数据检索。

通过这种存档数据的方式,您将拥有以下优势

  • 跟踪历史记录中的所有数据
  • 无论已删除记录的表结构如何,只有一个地方存档任何表中的记录
  • 不用担心原始表中的唯一索引
  • 不用担心检查原始表中的外部索引
  • 每个查询中不再有 WHERE子句用于检查删除

这已经是一个讨论 这里解释原因 软删除在实践中并不是一个好主意 未来可能出现的一些潜在问题,如计数记录、 ..。

优点是数据保存/永久化。缺点是在查询或检索具有大量软删除的表时性能下降。

在我们的例子中,我们使用两者的组合: 正如其他人在前面的答案中提到的,例如,我们 soft-delete users/clients/customers,和 items/products/merchandise表中的 hard-delete,其中存在不需要保留的重复记录。

视乎个别情况而定,请考虑以下事项:

通常,您不需要“软删除”记录。 保持简单快捷。 例如,删除一个不再可用的产品,这样你就不必检查你的应用程序(数量、产品列表、推荐产品等等)中的产品是否被软删除了。

然而,您可能会考虑数据仓库模型中的“软删除”。您正在查看已删除产品的旧收据。*

我只是想扩展一下上面提到的 独特的约束问题。

假设我有一个包含两列的表: 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需要更新到 (max. number of repetitions on soft-deleted rows) + 1.

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

对每一个唯一的列重复这个步骤!

我认为这个解决方案实在是太蹩脚了,应该使用一个单独的 存档表来存储已删除的行。