最近几天有点忙,所以我们今天来一篇短的,简单地介绍一下数据库设计中的一种模式——Soft Delete。

  可以说,该模式毁誉参半,甚至有非常多的人认为该模式是一个Anti-Pattern。因此在本篇文章中,我们不仅仅会对该模式进行介绍,同时也会列出该模式可能导致的一系列问题,以帮助大家正确地决定是否使用该模式。

Soft Delete简介

  首先先来想一个需求,那就是对用户操作的回滚支持。例如我现在正在用Word编写这篇文章。当我执行了一个错误操作的时候,我仅仅需要键入Ctrl + Z就可以进行回滚。而在有些Web应用中,我们同样需要这种功能。

  例如Rally是一个Web应用,以用来在软件开发过程中对进度和任务进行管理。在任务管理功能中,每次对任务的创建,修改以及删除都会被记录在系统中。

  现在问题来了,如果需要支持回滚,那么系统该如何记录一条已经被用户删除了的任务呢?最直观的想法就是在数据库中添加一列deleted来记录该任务是否已经被删除:

 @Entity
class Task extends … {
private boolean deleted;
……
private boolean isDeleted() {
return deleted;
} private void setDeleted(boolean deleted) {
this.deleted = deleted;
}
}

  如果一个任务被删除了,那么它的deleted将为true。也就是说,在用户删除一个任务的时候,系统实际上并没有将该任务彻底地从数据库中删除,而仅仅是通过deleted来标示其已经被删除了。而在恢复该任务的时候,只需要将deleted设置为false即可。

  OK,这就是有关Soft Delete模式的介绍。是不是很简单?但也正是因为它非常简单,进而导致了对它的滥用,从而使它成为了一个很多人眼中的Anti-Pattern。这种事情在IT技术中发生的还真是不少。最简单的就是Java的Checked Exception。的确它是一个好的功能,让使用Java编程变得更加严谨。但是过分的滥用导致很多类库都将用户完全无法处理的异常暴露在了类库接口中,反而使很多软件开发人员养成了直接忽略所有异常的坏习惯:

 try {
obj.someFunction();
} catch (Throwable e) {
}

  相信读者已经看出了这么做的危害: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’的条件:

 SELECT COUNT(*)
FROM project, story, task
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的自定义删除逻辑中完成该功能。

  好了,今天就到这里。

数据库设计中的Soft Delete模式的更多相关文章

  1. MySQL优化技巧之四(数据库设计中的一些技巧)

    1. 原始单据与实体之间的关系 可以是一对一.一对多.多对多的关系.在一般情况下,它们是一对一的关系:即一张原始单据对应且只对应一个实体.在特殊情况下,它们可能是一对多或多对一的关系,即一张原始单证对 ...

  2. SQL语句的使用,SELECT - 从数据库表中获取数据 UPDATE - 更新数据库表中的数据 DELETE - 从数据库表中删除数据 INSERT INTO - 向数据库表中插入数据

    SQL DML 和 DDL 可以把 SQL 分为两个部分:数据操作语言 (DML) 和 数据定义语言 (DDL). SQL (结构化查询语言)是用于执行查询的语法. 但是 SQL 语言也包含用于更新. ...

  3. Django数据库设计中字段为空的方式

    今天在做数据库设计的时候,设计了如下User表,其中我把email和phone字段设置为允许为空: class User(models.Model): username = models.CharFi ...

  4. 范式及其在mysql数据库设计中的应用

    一.什么是范式 1.1.范式:Normal Format,是离散数学的知识,是为了解决数据的存储与优化而提出来的.要求存储数据后,凡是能够通过关系寻找出来的数据,坚决不再重复存储,终极目标是为了减少数 ...

  5. MongoDB数据库设计中6条重要的经验法则

    Part 1 原文:6 Rules of Thumb for MongoDB Schema Design: Part 1 By William Zola, Lead Technical Support ...

  6. 解决Sybase PowerDesigner 数据库设计中 Name 自动填充Code

    在使用 Sybase PowerDesigner 进行数据库设计时,为了理清思路,需要将name改为中文名称,但是这个软件会自动将name填 充为code,可以通过如下配置修改: 选择tools-&g ...

  7. <<MySchool数据库设计优化>> 内部测试

    1) 在SQL Server 中,为数据库表建立索引能够( C ). A. 防止非法的删除操作 B. 防止非法的插入操作 C. 提高查询性能 D. 节约数据库的磁盘空间 解析:索引的作用是通过使用索引 ...

  8. 《MySchool数据库设计优化》内部测试

    1) 在SQL Server 中,为数据库表建立索引能够( C ). A. 防止非法的删除操作 B. 防止非法的插入操作 C. 提高查询性能 D. 节约数据库的磁盘空间 解析:索引的作用是通过使用索引 ...

  9. 数据库设计的误区—>CHAR与VARCHAR

    字符型字段是数据库表中最常见的字段,而字符型字段又分为定长和变长两种.一般来说,VARCHAR类型用于存储内容长度变化较大的数据,CHAR类型用于存储内容长度没有变化或变化不大的数据. 在数据的内部存 ...

随机推荐

  1. ExtJS 4.2 组件介绍

    目录 1. 介绍 1.1 说明 1.2 组件分类 1.3 组件名称 1.4 组件结构 2. 组件的创建方式 2.1 Ext.create()创建 2.2 xtype创建 1. 介绍 1.1 说明 Ex ...

  2. OpenCASCADE Expression Interpreter by Flex & Bison

    OpenCASCADE Expression Interpreter by Flex & Bison eryar@163.com Abstract. OpenCASCADE provide d ...

  3. input[tyle="file"]样式修改及上传文件名显示

    默认的上传样式我们总觉得不太好看,根据需求总想改成和上下结构统一的风格…… 实现方法和思路: 1.在input元素外加a超链接标签 2.给a标签设置按钮样式 3.设置input[type='file' ...

  4. UWP开发之Template10实践二:拍照功能你合理使用了吗?(TempState临时目录问题)

    最近在忙Asp.Net MVC开发一直没空更新UWP这块,不过有时间的话还是需要将自己的经验和大家分享下,以求共同进步. 在上章[UWP开发之Template10实践:本地文件与照相机文件操作的MVV ...

  5. iOS controller解耦探究实现——第一次写博客

    大学时曾经做过android的开发,目前的工作是iOS的开发.之前自己记录东西都是通过自己比较喜欢的笔记类的应用记录下了.直到前段时一个哥们拉着我注册了一个博客.现在终于想明白了,博客这个东西受众会稍 ...

  6. 【JQ基础】数组

    each() 方法规定为每个匹配元素规定运行的函数.

  7. App解读

    一直不懂别人口中说的原生开发.混合式开发.今天突然看了一篇文章讲解的是什么叫做原生App?移动 Web App?混合APP?分享给大家. 原生App是专门针对某一类移动设备而生的,它们都是直接安装到设 ...

  8. git和pycharm管理代码

    首先明白三个概念,服务器代码库,本地代码库,和正在coding的项目. coding完毕后,先通过commit提交到本地代码库,然后通过push再提交server的代码库    git步骤 git c ...

  9. IP报头

      位字段的值设置为二进制的0100表示IP版本4(IPv4).设置为0110表示IP版本6(IPv6)   位,它表示32位字长的IP报头长度,设计报头长度的原因是数据包可选字段大小会发生变化.IP ...

  10. TCP服务和首部知识点小结

    服务 应用程序会被TCP分割成数据段,而UDP不分割. TCP有超时重传和确认 如果检验和出错将丢弃 IP数据包可能会失序或者重复,所以TCP会处理 滑动窗口来进行流量控制 对字节流的内容不做任何解释 ...