2010-10-06 179 views
114

有些同事和我讨论了存储历史数据的最佳方式。目前,对于某些系统,我使用单独的表来存储历史数据,并且保留当前活动记录的原始表。所以,假设我有桌子FOO。在我的系统下,所有活动记录都将以FOO形式出现,所有历史记录将以FOO_Hist形式出现。用户可以更新FOO中的许多不同字段,因此我想对所有更新的内容都进行准确的说明。 FOO_Hist与FOO保持完全相同的字段,但自动递增的HIST_ID除外。每次FOO更新时,我都会在FOO_Hist中执行一条插入语句,类似于:insert into FOO_HIST select * from FOO where id = @id如何存储历史数据

我的同事说这是不好的设计,因为由于历史原因,我不应该有一张表的确切副本,应该用一个标志表示它是出于历史目的而插入到活动表中的另一条记录。

有处理历史数据存储的标准吗?在我看来,我不想将我的活跃记录与我所有的历史记录混在一张表中,考虑到它可能超过一百万条记录(我在长期考虑)。

你或你的公司如何处理这个问题?

我正在使用MS SQL Server 2008,但我想保留通用和任意DBMS的答案。

回答

50

直接在操作系统内支持历史数据将使您的应用程序比其他情况更加复杂。一般来说,我不会推荐这样做,除非您有严格的要求来操纵系统中的历史记录版本。

如果你仔细观察,对历史数据的大多数需求分为两类:

  • 审计日志:这是最好与审计表来完成。编写一个工具可以非常容易地生成脚本,通过从系统数据字典中读取元数据来创建审计日志表和触发器。这种类型的工具可以用来改进大多数系统上的审计日志记录。如果要实施数据仓库,也可以使用此子系统来更改数据捕获(请参阅下文)。

  • 历史报表:报告历史状态,按原样处理或随时间变化的分析报告。通过查询上述类型的审计日志表可以满足简单的历史报告要求。如果您有更复杂的需求,那么为报告实施数据集市可能更经济,而不是尝试将历史直接集成到运营系统中。

    缓慢变化的尺寸是迄今为止用于追踪和查询历史状态的最简单机制,并且大部分历史追踪都可以实现自动化。通用处理程序并不难写。通常,历史报告不必使用最新的数据,因此分批刷新机制通常很好。这使您的核心和报告系统架构相对简单。

如果您的要求属于这两个类别之一,那么最好不要将历史数据存储在操作系统中。将历史功能分离到另一个子系统中可能会减少总体工作量,并产生事务性和审计/报告数据库,这些数据库对于其预期用途而言效果更好。

+0

我想我明白你在说什么。所以我对我的FOO_Hist表做了真正的创建一个审计表。我没有在更新中使用触发器插入审计表,而是在程序中运行了一条语句。那是对的吗? – Aaron 2010-10-06 15:57:13

+4

非常。不过,使用触发器进行这种审计日志记录会更好;触发器确保任何更改(包括手动数据修复)都会记录在审计日志中。如果您有超过10-20个表来审计,则可能会更快地构建触发生成器工具。如果审计日志的磁盘通信量有问题,则可以将审计日志表放在单独的一组磁盘上。 – ConcernedOfTunbridgeWells 2010-10-06 15:59:52

+0

是的,我100%同意这一点。谢谢。 – Aaron 2010-10-06 17:17:55

0

你可以将表格分区吗?

“使用SQL Server 2008分区表和索引策略当数据库表增长到几百GB或更多时,加载新数据,删除旧数据和维护索引可能变得更加困难。如果表的大小足以导致此类操作花费更长时间,则即使是必须加载或删除的数据也可能非常大,从而导致表上的INSERT和DELETE操作不切实际。Microsoft SQL Server 2008数据库软件提供了表分区操作更易于管理。“

+0

是的,我可以分区表,但是,标准与历史数据处理时?历史数据是否应该与活动数据包含在同一个表中?这些是我想讨论的问题。这也不是任意的,因为它涉及到SQL Server 2008. – Aaron 2010-10-06 15:42:28

24

我不认为有一个特定的标准方式做到这一点,但我想我会抛出一种可能的方法。我在Oracle和我们的内部Web应用程序框架中工作,该框架利用XML来存储应用程序数据。

我们使用一种叫做主 - 详细信息模型,它是最简单的组成:例如称为Widgets往往只是包含ID

主表。通常会包含不会随时间变化的数据/不是历史数据。

详细内容/历史表例如称为Widget_Details含有至少:

  • ID - 主键。详细内容/历史ID
  • MASTER_ID - 例如在这种情况下被称为“WIDGET_ID”,这是FK到主记录
  • START_DATETIME - 时间戳指示数据库行
  • END_DATETIME的开始 - 时间戳指示的端部该数据库行
  • STATUS_CONTROL - 单个字符列指示该行的状态。 'C'表示当前,NULL或'A'将被历史/存档。我们只能用这个,因为我们不能在END_DATETIME指数为NULL
  • CREATED_BY_WUA_ID - 商店导致该行创建
  • XMLDATA的帐户的ID - 存储的实际数据

所以基本上,一个实体首先在主数据库中有1行,在细节中有1行。具有NULL结束日期和'C'的STATUS_CONTROL的细节。 发生更新时,当前行更新为当前时间的END_DATETIME,并且status_control设置为NULL(如果需要,则为'A')。在详细信息表中创建了一个新行,该行仍与同一主数据库相连,其中有status_control'C',进行更新的人员的id以及存储在XMLDATA列中的新数据。

这是我们历史模型的基础。创建/更新逻辑在Oracle PL/SQL包中处理,因此您只需将函数传递给当前ID,用户标识和新XML数据,并在内部执行所有行更新/插入操作以表示历史模型。开始和结束时间表示表格中该行何时处于活动状态。

存储很便宜,我们通常不会删除数据,而更愿意保留审计线索。这使我们可以在任何给定的时间查看我们的数据。通过索引status_control ='C'或使用视图,混乱并不是一个问题。显然你的查询需要考虑到你应该总是使用当前(NULL end_datetime和status_control ='C')版本的记录。

+0

嗨克里斯,如果你这样做,身份证(主键)必须改变吗?如果另一个表使用另一个表的关系,那么它又如何呢? – projo 2013-09-10 00:26:36

+0

@projo你的主表上的ID是PK,在概念上是你处理任何概念的“PK”。详细信息表上的ID是用于标识主服务器的历史版本(这是详细信息的另一列)的PK。在建立关系时,您经常会引用您的概念的真实PK(即主表上的ID或您的详细信息上的MASTER_ID列),并使用STATUS_CONTROL ='C'来确保您获得当前版本。或者,您可以引用详细信息ID以将某些事物与特定时间点相关联。 – 2013-11-06 14:05:34

+0

+1我已经在几个大型项目上取得了巨大成功。 – 2014-02-03 12:28:26

7

我觉得你的做法是正确的。历史表应该是没有索引的主表副本,请确保您在表中也有更新时间戳。

如果你尝试其他方法很快你将面对的问题:

  • 维护开销
  • 多个标志中选择
  • 查询放缓表
  • 增长,指标
0

真正的问题是你需要将历史数据和活动数据一起用于报告吗?如果是这样,请将它们保存在一张表中,然后为活动记录中的活动记录创建一个视图以供活动查询使用如果你只需要偶尔看看他们(研究法律问题或其他问题),然后把它们放在一个单独的表格中。

+1

在几个历史报告中“加入”两张表格更加困难,还是难以修改每个表格插入/更新/删除以了解历史问题?实际上,审计日志甚至会包含历史表中的当前数据,因此在报表中甚至不需要当前表。 – 2013-06-13 14:29:31

-1

另一种选择是归档[每日|每小时|无论]运行数据基础。大多数数据库引擎support the extraction of the data into an archive

基本上,这个想法是创建一个计划Windows或定时任务

  1. 确定在操作数据库
  2. 当前表从每个表选择所有数据到CSV或XML文件
  3. 将导出的数据压缩为ZIP文件,最好使用文件名中的生成时间戳以便于归档。

许多SQL数据库引擎配备了可用于这一目的的工具。例如,在Linux上使用MySQL时,下面的命令可以在cron作业用来调度提取:

mysqldump --all-databases --xml --lock-tables=false -ppassword | gzip -c | cat > /media/bak/servername-$(date +%Y-%m-%d)-mysql.xml.gz 
+0

你能解释一下downvotes的原因吗? – hamdiakoguz 2016-11-30 17:56:27

+0

这对历史数据并不合适,因为如果有人在存档周期内更改了一个值并将其更改回来,则更新将丢失。也没有简单的方法来查看随着时间的推移对一个实体的更改或部分恢复实体。 – Sgoettschkes 2017-01-19 08:56:55

0

我知道这个老的文章,但只是想补充几点。 这种问题的标准是最适合这种情况的标准。了解这种存储的需求以及潜在使用历史/审计/更改跟踪数据非常重要。

审计(安全目的):对所有可审计表使用公用表。定义结构来存储列名,值和值之后的字段。

存档/历史:像以前跟踪地址,电话号码等,创建一个单独的表FOO_HIST情况下是更好,如果你的活动事务表架构不显著在未来的(改变,如果你的历史记录表必须有相同的结构)。 如果您预计表格规范化,数据类型更改添加/删除列,请以xml格式存储您的历史数据。定义一个包含以下列(ID,日期,架构版本,XMLData)的表格。这将轻松处理模式更改。但你必须处理XML,这可能会引起一定程度的数据检索复杂性。

0

只是想添加一个选项,我开始使用,因为我使用Azure SQL,而多表的事情对我来说太麻烦了。我在我的表上添加了插入/更新/删除触发器,然后使用“FOR JSON AUTO”功能将前/后更改转换为json。

SET @beforeJson = (SELECT * FROM DELETED FOR JSON AUTO) 
SET @afterJson = (SELECT * FROM INSERTED FOR JSON AUTO) 

这会在更改之前/之后返回记录的JSON表示。然后,我将这些值存储在历史记录表中,其中包含发生更改的时间戳(我也存储当前关注记录的ID)。使用序列化过程,我可以控制在模式更改情况下如何回填数据。

我得知这个从这个链接here