最近几天有点忙,所以我们今天来一篇短的,简单地介绍一下数据库设计中的一种模式——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. mobx @computed的解读

    写在前面:我一开始看不懂官网的@computed的作用,因为即使我把@computed去掉,依然能正确的report,然后我百度谷歌都找不到答案,下面都是我自己的理解,如果是有问题的,不对的,请务必留 ...

  2. 札记:Fragment基础

    Fragment概述 在Fragment出现之前,Activity是app中界面的基本组成单位,值得一提的是,作为四大组件之一,它是需要"注册"的.组件的特性使得一个Activit ...

  3. console的高级使用

    1.console.table()用来表格化展示数据. var people = { zqz: { name: 'zhaoqize', age: 'guess?' }, wdx: { name: 'w ...

  4. 学习ASP.NET Core, 怎能不了解请求处理管道[5]: 中间件注册可以除了可以使用Startup之外,还可以选择StartupFilter

    中间件的注册除了可以借助Startup对象(DelegateStartup或者ConventionBasedStartup)来完成之外,也可以利用另一个叫做StartupFilter的对象来实现.所谓 ...

  5. SQL Server-聚焦IN VS EXISTS VS JOIN性能分析(十九)

    前言 本节我们开始讲讲这一系列性能比较的终极篇IN VS EXISTS VS JOIN的性能分析,前面系列有人一直在说场景不够,这里我们结合查询索引列.非索引列.查询小表.查询大表来综合分析,简短的内 ...

  6. 使用 JavaScript 和 canvas 做精确的像素碰撞检测

    原文地址:Pixel accurate collision detection with Javascript and Canvas 译者:nzbin 我正在开发一个需要再次使用碰撞检测的游戏.我通常 ...

  7. 多线程爬坑之路-Thread和Runable源码解析

    多线程:(百度百科借一波定义) 多线程(英语:multithreading),是指从软件或者硬件上实现多个线程并发执行的技术.具有多线程能力的计算机因有硬件支持而能够在同一时间执行多于一个线程,进而提 ...

  8. Oracle碎碎念~2

    1. 如何查看表的列名及类型 SQL> select column_name,data_type,data_length from all_tab_columns where owner='SC ...

  9. Hbase的伪分布式安装

    Hbase安装模式介绍 单机模式 1> Hbase不使用HDFS,仅使用本地文件系统 2> ZooKeeper与Hbase运行在同一个JVM中 分布式模式– 伪分布式模式1> 所有进 ...

  10. 在开源中国(oschina)git中新建标签(tags)

    我今天提交代码到主干上面,本来想打个标签(tags)的. 因为我以前新建过标签(tags),但是我现在新建的时候不知道入库在哪了.怎么找也找不到了. 从网上找资料也没有,找客服没有人理我,看到一个交流 ...