例如,我有一个存储有关属性的详细信息的表。这可能有所有者,价值等。
是否有一个好的设计来保持每个变化的历史记录对所有者和价值。我想为很多桌子做这个。有点像对表的审核。
我认为保留带有字段的单个表
table_name
,field_name
,prev_value
,current_val
,time
,user
。
但它看起来有点hacky和丑陋。有更好的设计吗?
感谢。
答案 0 :(得分:23)
有几种方法
基于字段
audit_field (table_name, id, field_name, field_value, datetime)
这个可以捕获所有表的历史记录,并且很容易扩展到新表。新表不需要对结构进行任何更改。
Field_value有时被拆分为多个字段,以原生支持原始表中的实际字段类型(但只会填充其中一个字段,因此数据被非规范化;一个变体是将上表拆分为一个表每种类型)。
其他元数据如field_type,user_id,user_ip,action(更新,删除,插入)等可能很有用。
这些记录的结构很可能需要转换才能使用。
基于记录
audit_table_name (timestamp, id, field_1, field_2, ..., field_n)
对于数据库中的每种记录类型,创建一个通用表,其中包含所有字段作为原始记录,以及版本控制字段(可能再次使用其他元数据)。每个工作表都需要一张表。创建此类表的过程可以自动完成。
这种方法为您提供了与主数据结构非常相似的语义丰富的结构,因此用于分析和处理原始数据的工具也可以很容易地用于此结构。
日志文件
前两种方法通常使用非常轻微索引的表(或根本没有索引且没有参照完整性),从而最大限度地减少写入惩罚。尽管如此,有时平板日志文件可能是首选,但当然功能上大大减少了。 (基本上取决于您是否希望实际的审核/日志将由其他系统进行分析,或者历史记录是主系统的一部分)。
答案 1 :(得分:6)
另一种看待这种情况的方法是对数据进行时间维度。
假设您的表格如下:
create table my_table (
my_table_id number not null primary key,
attr1 varchar2(10) not null,
attr2 number null,
constraint my_table_ak unique (attr1, att2) );
然后,如果您改变它:
create table my_table (
my_table_id number not null,
attr1 varchar2(10) not null,
attr2 number null,
effective_date date not null,
is_deleted number(1,0) not null default 0,
constraint my_table_ak unique (attr1, att2, effective_date)
constraint my_table_pk primary key (my_table_id, effective_date) );
您可以拥有my_table,在线和可用的完整运行历史记录。您必须更改程序的范例(或使用数据库触发器)来拦截UPDATE活动到INSERT活动,并将DELETE活动更改为UPDATing IS_DELETED布尔值。
非理性:
你说这个解决方案类似于基于记录的审计是正确的;我最初读它是一个字段连接成一个字符串,我也看到了。道歉。
我在表的时间维度和使用基于记录的审计中心之间看到的主要区别在于可维护性而不牺牲性能或可伸缩性。
可维护性:如果对主表进行结构更改,则需要记住更改影子表。同样,需要记住对执行更改跟踪的触发器进行更改,因为此类逻辑无法在应用程序中生存。如果使用视图来简化对表的访问,您还必须更新它,并更改反对它的反向触发器来拦截DML。
在时间尺寸的表格中,您可以进行所需的结构更改,然后就完成了。作为一个遗留项目的FNG的人,这种清晰度值得赞赏,特别是如果你不得不进行大量的重构。
性能和可伸缩性:如果在有效/到期日期列中对时间维度表进行分区,则活动记录位于一个“表”中,而非活动记录位于另一个“表”中。究竟如何比您的解决方案更具可扩展性? “删除”和活动记录涉及Oracle中的行移动,这是一个删除和插入 - 正是基于记录的解决方案所需要的。
性能的另一面是,如果应用程序在某个日期查询记录,则分区消除允许数据库仅搜索记录所在的表/索引;搜索活动和非活动记录的基于视图的解决方案需要UNION-ALL,而不使用这样的视图需要将UNION-ALL放在任何地方,或者使用某种“看这里,然后看那里”的逻辑应用程序,我说:blech。
简而言之,这是一个设计选择;我不确定是对还是错。
答案 2 :(得分:4)
在我们的项目中,我们通常这样做: 你有一张桌子
properties(ID, value1, value2)
然后你添加表
properties_audit(ID, RecordID, timestamp or datetime, value1, value2)
ID
- 是历史记录的ID(不是真的需要)
RecordID
- 指向原始属性表中的记录。
当您更新properties
表时,您会将新记录添加到properties_audit
,并在properties
中更新以前的记录值。这可以使用触发器或在DAL中完成。
之后,properties
中的最新值和properties_audit
中的所有历史记录(之前的值)。
答案 3 :(得分:2)
我认为更简单的架构将是
table_name, field_name, value, time, userId
无需在审计表中保存当前值和以前的值。当您对任何字段进行更改时,您只需在审计表中添加一行并使用更改的值。这样,您始终可以按时对审计表进行排序,并在更改之前了解字段中的先前值。