【问题标题】:Deleting database records unpermenantley (soft-delete)永久删除数据库记录(软删除)
【发布时间】:2011-03-09 03:46:45
【问题描述】:

故事

我将编写一些代码来管理我的应用程序中已删除的项目,但我将软删除它们,以便在需要时将它们返回。在隐藏或删除项目时,我的应用程序逻辑中有一个层次结构需要尊重。

我逻辑上将我的物品放在国家、城市、地区和品牌的三个容器中。 每个项目都应该属于一个国家、一个城市、一个地区和一个品牌。 现在,如果我删除一个国家,它应该删除属于给定国家的城市、地区、品牌和项目。如果我删除了城市,它也应该删除它下面的全部内容(地区、品牌等)


注意事项

当我删除一个国家/地区并删除关联品牌时,我应该注意一个品牌可能在多个国家/地区拥有商品。


问题

你有什么建议

  1. 将项目(无论是国家、城市、项目等)标记为已删除,这将需要大量代码来检查每次从数据库加载任何项目时是否已删除以及一些额外的字段来标记它所属的城市是否被删除,它所属的国家是否被删除等等。

  2. 将删除的内容分别移动到特定的表(DeletedCountries、Deleted Cities 等) 并保存与之关联的项目的 ID,以便稍后我可以将它们插入到它的原始表中。当然,这将为我的应用程序节省所有能够检查所有已删除项目并确保删除所有层次结构的代码。

  3. 也许你有更好的方法/建议/想法来实现这样的事情!

【问题讨论】:

  • 由于层次结构问题,我实际上会选择不太理想的#2。它将为您节省编码烦恼和永久的where isdeleted<>1 烦恼。 (这可能会稍微加快 sql server)

标签: c# asp.net sql database


【解决方案1】:

为了论证,解决方案#2(将已删除的项目移动到它们自己的表中)的一个优点是,如果您有很多很多记录,您不必担心根据“已删除”状态对记录进行索引。

话虽如此,如果我要将数据从一个表“移动”到另一个表(通过删除后插入),我会确保在 1 个事务中完成。

【讨论】:

  • 所以你认为我应该选择选项#2,但你的建议是在一个事务中进行插入和删除,你说有一个同时具有插入和删除的存储过程删除语句?
  • 确实如此。我之所以这么说是因为如果您的代码在没有相应的delete 的情况下执行insert(反之亦然),您的数据库将处于不一致的状态。使用事务,您可以提交或回滚一系列 sql 语句。此页面上的最后一个存储过程 (4guysfromrolla.com/webtech/041906-1.shtml) 有一个使用 try / catch 的示例。
  • 在这种情况下,对于属于已删除品牌的元素,如何处理参照完整性?即使在单个事务中提交了 DELETE 和 INSERT,也不能存在 FOREIGN KEY 引用,因此数据很容易出现不一致。
【解决方案2】:

我现在正在使用一种技术,我们在数据库中的每个用户维护的表上存储一个“DeleteDate”。 DeleteDate 字段是 smalldatetime 数据类型,默认值为 6/1/2079

结合 DeleteDate 字段上的索引,我们可以使用标准视图或用户定义函数仅返回“当前”记录(即删除日期在未来的那些记录)。在查找当前数据时,所有查询都会通过该索引进行路由,而删除则成为一个微不足道的更新查询。

需要对相关表进行一些额外的逻辑检查。但这是永远不必担心用户“意外”删除有价值数据的部分代价。

以后当这些表过大,有很多被删除的记录时,我们可以在DeleteDate上先对表进行分区。这会将所有“已删除”记录从“实时”记录中移走。

【讨论】:

  • +1 用于指出如何处理真正删除的项目。我在当前项目中使用了类似的方法。
  • 我对有关此解决方案的更多信息感兴趣,可能是对博客文章中特定示例的引用或其他内容。我想了解更多关于这个解决方案如何随着时间的推移工作,如何“通过这个索引路由[所有查询]”,它如何与想要查询和更新同一个对象的 ORM 交互,而不是查询和表的视图更新。
【解决方案3】:

将项目标记为删除确实使信息检索复杂化,并且您需要自己处理级联删除。

我会选择“邮箱”方法,将已删除的记录移动到不同的表中。我已经完成了一个使用软删除的项目,我最终将所有删除调用都放到了存储过程中,并在存储过程中处理了复制和删除。

【讨论】:

    【解决方案4】:

    您应该通过将所有子项标记为已删除来管理您的层次结构。这样,如果您的例如。产品属于品牌,只有品牌被删除才能查看。您还应该将您的逻辑放在数据检索方面,以避免不必要地收集已删除的信息。

    SELECT
      *
    FROM
      products p,
      category c
    WHERE
      p.catId = c.Id
      AND NOT c.Deleted
    

    最重要的是,应将有关已删除类别的信息编入索引。

    CREATE PRIMARY INDEX ON category (Id)
    CREATE INDEX ON category (Deleted)
    

    CREATE INDEX ON category (Id, Deleted)
    

    【讨论】:

    • 但这需要我为管理页面构建一个全新的 DAL,它将返回所有数据(无论是否删除)
    • 您应该在设计 DAL 时考虑到处理已删除列。如果这样做,区别只是传递了一个布尔标志,它决定是否要显示已删除的元素。
    • 关于您的编辑,Id 已经是一个主键,默认情况下是一个 Index .. 所以我应该只索引已删除的! .. 很抱歉,这超出了这个问题的主题,但你现在让我对此感到困惑!
    • 你说得对,我只是修改了示例,代码只是为了说明这个想法,它不是特定于 SQL Server 的。
    • 我的意思是我的数据库中的 Id 列已经是主键了,这不是说它已经在使用集群索引了吗/我必须向它添加索引吗? .. 顺便说一句,感谢索引提示 +1
    【解决方案5】:

    我认为标记该项目是最好的方法,甚至我也使用邮件方法来软删除。

    是的,这需要考虑很多东西来管理,但我没有找到任何其他方法。我只是为每个数据类型为 bit 的 Status 表添加一个额外的列。

    谢谢

    【讨论】:

    • 但是在“每次”显示之前检查项目是否被删除不影响应用程序的性能!而且为了管理我拥有的层次结构,它不会只停留在一个列上,我必须向层次结构中较少的对象添加一个额外的列来检查它的父级是否被删除所以它应该也被删除! ..你能告诉我你对我的第二种方法有什么看法吗:)?非常感谢您的宝贵时间!
    • 是的,它会影响一些性能,但您可以根据您的应用程序强度来管理性能。但我认为,如果您将在其他表中添加记录以使条目删除,那么它将产生更多的开销。
    • 对不起,我不明白为什么有一个单独的表会是一个开销! ..我会删除一个项目,它已经全部移到另一个表中,并且我不必在显示项目时检查它们! ..我会在搬很多东西的时候付出性能成本,但这会在我(管理员)看来性能,并且所有者可以面对一些延迟,以便让我的访问者有更好的体验(更快的应用程序).​​.我是否遗漏了什么或想错了?
    【解决方案6】:

    您要求的删除技术有多复杂?

    只需一个日期字段且没有审核日志,您就可以拥有一个即时删除标志。如果 datefield 为空,则它没有被删除。然后,您可以在索引上使用该日期字段(如果索引允许空值)。

    如果您想要更复杂的东西,那么您可以使用额外的表格。你会允许它被删除、取消删除、重新删除并保留每一个的记录吗?如果是这样,为操作日志保留一个单独的表,并只保留一个带有布尔字段的记录(实际上,该表上的连接可能更快,取决于数据)

    【讨论】:

      【解决方案7】:

      如果您经常重新组合项目,则标记是一种更可取的方式,但您最终不得不更改数据访问权限以避免显示已标记的项目,如果您已经设置了大量代码,这可能会相当痛苦访问您的数据,因此如果您有很多访问数据的“遗留”代码,移动可能会更好。如果这种情况很少见,并且您也对历史日志感兴趣,那么移动到另一个数据库表效果很好。

      实现这两者的一种简单方法是使用触发器来更改删除行并执行操作。但是,如果您确实需要删除项目,那么当您标记而不是移动项目时,标记选项将变成皇家 PITA。在许多情况下触发器更容易的原因是您可以捕获每个删除,而不仅仅是那些由代码启动的。

      【讨论】:

      • 属于已删除品牌的对象的参照完整性如何?
      猜你喜欢
      • 1970-01-01
      • 2022-01-20
      • 2015-09-12
      • 2010-10-04
      • 2016-10-01
      • 1970-01-01
      • 1970-01-01
      • 2013-11-07
      • 1970-01-01
      相关资源
      最近更新 更多