通过软删除保持逻辑一致性,同时保留原始信息

时间:2012-05-01 18:47:57

标签: sql database-design soft-delete

我有一个非常简单的表students,结构如下,其中主键为id。这个表是大约20个数百万行表的替身,它们很多联合在一起。

+----+----------+------------+
| id |   name   |    dob     |
+----+----------+------------+
|  1 | Alice    | 01/12/1989 |
|  2 | Bob      | 04/06/1990 |
|  3 | Cuthbert | 23/01/1988 |
+----+----------+------------+

如果Bob想要改变他的出生日期,那么我有几个选择:

  1. 使用新的出生日期更新students

    肯定: 1 DML操作;可以通过单个主键查找来访问该表。

    否定:我失去了Bob曾经认为他出生于04/06/1990的事实

  2. 在表格中添加一列created date default sysdate,然后将主键更改为id, created。每update成为:

    insert into students(id, name, dob) values (:id, :name, :new_dob)
    

    然后,每当我想要最新信息时,请执行以下操作(Oracle但问题代表每个RDBMS):

    select id, name, dob
      from ( select a.*, rank() over ( partition by id 
                                           order by created desc ) as "rank"
               from students a )
     where "rank" = 1
    

    肯定:我从不丢失任何信息。

    否定:整个数据库上的所有查询都需要更长的时间。如果表格是指示的大小,这无关紧要,但是一旦你使用范围扫描而不是独特的扫描开始产生影响,就会在你的第5 left outer join开始。

  3. 添加一个不同的列,deleted date default to_date('2100/01/01','yyyy/mm/dd')或任何过早或未来的日期,我的想象。将主键更改为id, deleted,然后每个update变为:

    update students x
       set deleted = sysdate 
     where id = :id
       and deleted = ( select max(deleted) from students where id = x.id );
    insert into students(id, name, dob) values ( :id, :name, :new_dob );
    

    以及获取当前信息的查询变为:

    select id, name, dob
      from ( select a.*, rank() over ( partition by id 
                                           order by deleted desc ) as "rank"
               from students a )
     where "rank" = 1
    

    肯定:我从不丢失任何信息。

    否定:两个DML操作;我仍然必须使用额外成本或范围扫描的排名查询,而不是在每个查询中使用唯一索引扫描。

  4. 创建第二个表格,例如student_archive,并将每个更新更改为:

    insert into student_archive select * from students where id = :id;
    update students set dob = :newdob where id = :id;
    

    肯定:永远不会丢失任何信息。

    否定: 2个DML操作;如果您想获得所有信息,则必须使用union或额外的left outer join

  5. 为了完整起见,有一个可怕的非规范化数据结构:id, name1, dob, name2, dob2...等。

  6. 如果我不想丢失任何信息并且总是进行软删除,则不能选择数字1。数字5可以安全地丢弃,因为它造成的麻烦超过它的价值。

    我留下了选项2,3和4以及随之而来的消极方面。我通常最终使用选项2和可怕的150行(间距很大)多个子选择连接。


    tl;博士我意识到我在这里的“非建设性”投票上滑行接近但是:

    从不删除任何数据时,保持逻辑一致性的最佳(单数!)方法是什么?

    有没有比我记录的更有效的方式?在这种情况下,我将高效定义为“更少的DML操作”和/或“能够删除子查询”。如果您能够在(如果)回答时想到更好的定义,请随意。

1 个答案:

答案 0 :(得分:1)

我会坚持#4进行一些修改。不需要从原始表中删除数据;在更新(或删除之前)原始记录之前,将旧值复制到存档表就足够了。这可以通过行级触发器轻松完成。在我看来,检索所有信息并不是经常的操作,我没有看到额外的join / union有什么问题。此外,您可以定义一个视图,因此从最终用户的角度来看,所有查询都是直截了当的。