最近几天有点忙,因此咱们今天来一篇短的,简单地介绍一下数据库设计中的一种模式——Soft Delete。数据库
能够说,该模式毁誉参半,甚至有很是多的人认为该模式是一个Anti-Pattern。所以在本篇文章中,咱们不只仅会对该模式进行介绍,同时也会列出该模式可能致使的一系列问题,以帮助你们正确地决定是否使用该模式。编程
Soft Delete简介数据库设计
首先先来想一个需求,那就是对用户操做的回滚支持。例如我如今正在用Word编写这篇文章。当我执行了一个错误操做的时候,我仅仅须要键入Ctrl + Z就能够进行回滚。而在有些Web应用中,咱们一样须要这种功能。函数
例如Rally是一个Web应用,以用来在软件开发过程当中对进度和任务进行管理。在任务管理功能中,每次对任务的建立,修改以及删除都会被记录在系统中。性能
如今问题来了,若是须要支持回滚,那么系统该如何记录一条已经被用户删除了的任务呢?最直观的想法就是在数据库中添加一列deleted来记录该任务是否已经被删除:this
1 @Entity 2 class Task extends … { 3 private boolean deleted; 4 …… 5 private boolean isDeleted() { 6 return deleted; 7 } 8 9 private void setDeleted(boolean deleted) { 10 this.deleted = deleted; 11 } 12 }
若是一个任务被删除了,那么它的deleted将为true。也就是说,在用户删除一个任务的时候,系统实际上并无将该任务完全地从数据库中删除,而仅仅是经过deleted来标示其已经被删除了。而在恢复该任务的时候,只须要将deleted设置为false便可。编码
OK,这就是有关Soft Delete模式的介绍。是否是很简单?但也正是由于它很是简单,进而致使了对它的滥用,从而使它成为了一个不少人眼中的Anti-Pattern。这种事情在IT技术中发生的还真是很多。最简单的就是Java的Checked Exception。的确它是一个好的功能,让使用Java编程变得更加严谨。可是过度的滥用致使不少类库都将用户彻底没法处理的异常暴露在了类库接口中,反而使不少软件开发人员养成了直接忽略全部异常的坏习惯:spa
1 try { 2 obj.someFunction(); 3 } catch (Throwable e) { 4 }
相信读者已经看出了这么作的危害:catch甚至将表示系统错误的Error类型实例都抓住了。但这里不能忽略的一个事实是,当软件开发人员对某些行为无能为力,那么他极有可能忽略某些编码准则,而首先选择使用一种能让系统在正确运行的状况下工做起来的方法。例如对于上面的函数调用obj.someFunction(),若是其抛出的异常和类库内部运行逻辑相关,并且每次均可能致使这种问题,那么软件开发人员就极有可能使用上面的代码忽略掉该异常。这种问题甚至在一些广为使用的类库中存在着。例如OData4j曾经把取得OData元数据时产生的全部异常都看成是目标服务没有暴露元数据的状况来处理。设计
OK,说得有点远了。总结起来就是,一旦一个技术过于简单并且可以处理某个状况,那么软件开发人员将不会仔细研究使用该技术所须要的语境,从而致使滥用。和Checked Exception同样,Soft Delete也是这样的一个例子。调试
Soft Delete的问题
那么该数据库模式有什么问题呢?简单地说,那就是太容易出错,并且是隐蔽的错误。试想一下,若是用户须要列出全部的任务,那么在SQL语句中就须要使用WHERE deleted = ‘N’这样的条件。并且该条件几乎在全部处理任务的SQL语句中都要出现。一旦在一个SQL语句中忘记了该条件,那么这极有多是一个Bug,并且这种Bug有时候还很是隐蔽。例如若是在一个COUNT语句中忘记标示了该条件,并且系统中任务不少,可是被删除的任务不多,那么该Bug可能存在几年都不会被发现。
同时若是一旦决定须要在系统中大量地使用Soft Delete,那么SQL将变得很是混杂。在统计功能中,咱们可能须要筛选出全部包含任务的用例的个数,甚至是包含这些用例的项目的个数,那么咱们就须要在SQL中同时标明多个WHERE deleted = ‘N’的条件:
1 SELECT COUNT(*) 2 FROM project, story, task 3 WHERE … project.deleted = ‘N’ AND story.deleted = ‘N’ AND task.deleted = ‘N’
那么在调试这些语句的时候,或者查看这些语句的执行计划以进行性能调优的时候,软件开发人员都会发现因为这些条件的引入致使SQL的执行变得很是复杂。
还有一个问题就是,若是一个系统经常执行对记录的软删除,那么数据库中所记录的数据将比实际所须要记录的数据多得多。这种垃圾数据可能会致使数据库的索引变得很大,甚至可能会由此而严重影响数据库的性能。
另外一个问题则和级联有关。数据库提供了级联操做,在删除一个数据记录的时候,数据库会根据该数据与其它记录之间的关联关系来自动完成对其它关联记录的操做。这也是数据库保持其数据完整性的一种方法。可是一旦用户使用了Soft Delete,那么在对其进行软删除的时候就不会将其从数据库中移除,那么与其关联的那些记录也便不会被数据库移除。也就是说,软件开发人员须要自行完成数据完整性的管理。除此以外,软件开发人员还须要在数据访问层(DAL,Data Access Layer)中完成事务的组织,而且一旦数据库表的定义发生了变化,这些事务组织的逻辑也可能须要进行更改。
Soft Delete的实现
也正是因为Soft Delete拥有这么多的问题,所以软件开发人员们提出了不少Soft Delete的变通实现方法,大大地减小了开发和维护Soft Delete模式数据的成本。
一种方法就是利用数据库所提供的View功能。在该方法中,咱们须要在数据库中建立一个View,以显示表中deleted值为’N’的各行数据。而在对数据进行操做的各SQL语句中,咱们只须要直接操做该View,从而避免了每次都须要在SQL语句中标明WHERE deleted = ‘N’这种条件。
而另外一种方法则是将这些数据分散到两个不一样的表中。这两个表中的一个表记录deleted值为’N’的各行数据,而另外一个表则记录已经被软删除的deleted值为’Y’的各行数据。并且在具备两个表的状况下,系统甚至能够很较为容易地实现垃圾箱的功能。
而在某些状况下,咱们也能够在数据库设计中借鉴Soft Delete的思路。Soft Delete须要用户自行管理数据库中数据的关联关系。这是一份额外的工做,但也带来了更多的灵活性。
Rally中的任务删除操做的回滚天然没必要多说,垃圾箱功能的添加也将变为很是容易的事。而对于某些自定义的删除逻辑,Soft Delete所带来的灵活性将更为突出。例如在Rally中,若是咱们须要实现“删除用户用例时若是用户用例中包含任务,那么这些任务将挪至父用例中”这样一个需求,那么咱们就能够在Soft Delete的自定义删除逻辑中完成该功能。
好了,今天就到这里。