我有一个非常简单的表students
,结构如下,主键在哪里id
。该表是大约 20 个数百万行的表的替身,这些表经常连接在一起。
+----+----------+------------+ | 编号 | 姓名 | 出生日期 | +----+----------+------------+ | 1 | 爱丽丝 | 1989 年 1 月 12 日 | | 2 | 鲍勃 | 1990 年 4 月 6 日 | | 3 | 卡斯伯特 | 1988 年 1 月 23 日 | +----+----------+------------+
如果 Bob 想更改他的出生日期,那么我有几个选择:
更新
students
新的出生日期。积极因素: 1 DML 操作;该表始终可以通过单个主键查找来访问。
负面因素:我失去了 Bob 曾认为他出生于 1990 年 4 月 6 日这一事实
在表中添加一列 ,
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
使用范围扫描而不是唯一扫描,就会开始产生影响。添加一个不同的专栏,
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 操作;我仍然必须使用额外成本的排名查询或范围扫描,而不是每个查询中的唯一索引扫描。
创建第二个表,说
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
.为了完整起见,有一个可怕的去规范化的数据结构:
id, name1, dob, name2, dob2...
等等。
如果我不想丢失任何信息并总是进行软删除,那么第 1 号不是一个选项。5 号可以安全地丢弃,因为它造成的麻烦多于它的价值。
我只剩下选项 2、3 和 4 以及随之而来的负面影响。我通常最终使用选项 2 和可怕的 150 行(间隔很好)多个子选择连接。
tl;博士我意识到我在这里的“非建设性”投票中滑到了接近线的位置,但是:
在不删除任何数据的同时保持逻辑一致性的最佳(单一!)方法是什么?
有没有比我记录的更有效的方法?在这种情况下,我将高效定义为“更少的 DML 操作”和/或“能够删除子查询”。如果您在(如果)回答时能想到更好的定义,请随意。