如何存储历史数据


162

我和一些同事就存储历史数据的最佳方法进行了辩论。当前,对于某些系统,我使用一个单独的表来存储历史数据,并且为当前的活动记录保留一个原始表。因此,假设我有表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的通用和任意答案。

Answers:


80

直接在操作系统中支持历史数据将使您的应用程序比以前更加复杂。通常,除非您有严格的要求在系统内处理记录的历史版本,否则我不建议您这样做。

如果仔细观察,对历史数据的大多数需求都属于以下两类之一:

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

  • 历史报告:关于历史状态,“原样”职位的报告或一段时间内的分析报告。通过查询上述类型的审核日志记录表,可以满足简单的历史报告要求。如果您有更复杂的要求,那么为报表实现数据集市可能比尝试将历史记录直接集成到操作系统中更为经济。

    到目前为止,缓慢更改尺寸是跟踪和查询历史状态的最简单机制,并且大多数历史跟踪可以自动进行。通用处理程序并不难编写。通常,历史报告不必使用最新数据,因此批量刷新机制通常很好。这样可以使您的核心和报告系统体系结构相对简单。

如果您的需求属于这两类之一,则最好不要在操作系统中存储历史数据。将历史功能分离到另一个子系统中,总体上可能会花费较少的精力,并且可以生成交易和审核/报告数据库,这些数据库可以更好地实现其预期目的。


我想我明白你在说什么。因此,我对FOO_Hist表所做的实际上是创建一个审计表。我没有使用触发器在更新时插入到审计表中,而是在程序中运行了一条语句。那是对的吗?
亚伦2010年

6
差不多了 不过,最好使用触发器来进行这种审核日志记录。触发器确保所有更改(包括手动数据修复)都记录在审核日志中。如果您要审核的表超过10-20个,则构建触发器生成器工具的总体速度可能会更快。如果审核日志的磁盘流量有问题,则可以将审核日志表放在单独的一组磁盘上。
ConcernedOfTunbridgeWells 2010年

是的,我100%同意。谢谢。
亚伦

40

我不认为有特定的标准方法,但我认为我会提出一种可能的方法。我在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-存储导致创建行的帐户的ID
  • XMLDATA-存储实际数据

因此,从本质上讲,一个实体首先要在主体中具有1行,在详细信息中具有1行。结束日期为NULL且STATUS_CONTROL为“ C”的明细。发生更新时,当前行将更新为具有当前时间的END_DATETIME,并且status_control设置为NULL(如果需要,则设置为“ A”)。在明细表中会创建一个新行,并仍然链接到同一主表,并带有status_control'C',进行更新的人员的ID和存储在XMLDATA列中的新数据。

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

存储价格便宜,我们通常不删除数据,而是希望保留审核记录。这使我们可以随时查看数据。通过为status_control ='C'编制索引或使用View,杂乱不成问题。显然,您的查询需要考虑到您应该始终使用记录的当前版本(NULL end_datetime和status_control ='C')。


克里斯,您好,如果您这样做,那么ID(主键)必须正确吗?如果另一个表使用了另一个表,该关系又如何呢?
projo 2013年

@projo主表上的ID是PK,从概念上讲,无论您要处理的是什么概念,其都是“ PK”。详细信息表上的ID是PK,用于标识主数据的历史版本(这是详细信息的另一列)。在建立关系时,您通常会引用概念的真实PK(即,主表上的ID或详细信息上的MASTER_ID列),并使用STATUS_CONTROL ='C'以确保获得最新版本。或者,您可以引用详细信息ID以将某些内容与特定时间点相关联。
克里斯·卡梅隆·米尔斯

+1我已经在多个大型项目中成功实施了这种模式。
三值逻辑

我们正在使用相同的方法。但是现在我想知道最好只存储START_DATETIME而不存储END_DATETIME
bat_ventzi 2015年

我的经验有几个变化。如果您的实体已“结束”,即已存档或删除,则实际上您可能没有带有“ C”状态控制的明细记录,即没有当前行,尽管您不知道何时发生。或者,您可以在最后一行上设置end_datetime,并且如果出现'end''C'行,则表明该实体现在已被删除/存档。最后,您可以通过可能已经拥有的另一列STATUS来表示这一点。
克里斯·卡梅隆·米尔斯

15

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

如果您尽快尝试其他方法,则会遇到问题:

  • 维护费用
  • 选择中的更多标志
  • 查询速度变慢
  • 表,索引的增长

7

SQL Server 2016及更高版本中,有一个名为“ 临时表”的新功能,旨在以最小的开发人员精力来解决这一难题。时态表的概念类似于更改数据捕获(CDC),不同之处在于时态表已抽象出了大多数使用CDC时必须手动执行的操作。




1

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

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

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

我得知这个从这个链接点击这里


0

您可以对表进行分区吗?

“使用SQL Server 2008进行分区的表和索引策略当数据库表的大小增长到数百GB或更多时,加载新数据,删除旧数据和维护索引会变得更加困难。仅表的绝对大小这会导致此类操作花费更长的时间。甚至必须加载或删除的数据也可能非常庞大,从而使对表的INSERT和DELETE操作变得不切实际。MicrosoftSQL Server 2008数据库软件提供了表分区功能,以使此类操作更易于管理。”


是的,我可以对表进行分区,但这是处理历史数据时的标准吗?历史数据应与活动数据包含在同一表中吗?这些是我想讨论的问题。因为它涉及到SQL Server 2008这还不算武断
阿伦

0

真正的问题是,您是否需要同时使用历史数据和活动数据进行报告?如果是这样,请将它们放在一个表中,进行分区并创建一个视图,以供活动记录用于活动查询中。如果您只需要偶尔查看它们(以研究法律问题等),则将它们放在单独的表格中。


2
JOIN在几个历史报告中创建两个表是否更加困难,还是为了了解历史问题而修改每个表的插入/更新/删除更加困难?实际上,审核日志甚至会将历史记录表中的当前数据包括在内,因此报表中甚至不需要当前表。

0

另一种选择是按[每日|每小时|任何时间]存档运行数据。大多数数据库引擎支持将数据提取到存档中

基本上,这个想法是创建一个计划的Windows或CRON作业,

  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

2
这一点根本不适用于历史数据,因为如果有人在归档周期内更改了值并将其更改回原来的值,则更新将丢失。也没有简便的方法来查看随时间变化的一个实体或部分还原一个实体。
Sgoettschkes

0

我知道这个旧帖子,但只想补充几点。这些问题的标准是最适合这种情况的标准。了解这种存储的必要性以及历史/审计/变更跟踪数据的潜在使用非常重要。

审核(出于安全目的):将公用表用于所有可审核表。定义结构以存储列名,值之前和之后的字段。

存档/历史记录:对于诸如跟踪先前的地址,电话号码等情况,创建单独的表FOO_HIST会更好,如果您的活动事务表架构将来不会发生重大变化(如果您的历史表必须具有相同的结构)。如果您期望表标准化,列的数据类型更改增加/删除,则以xml格式存储历史数据。使用以下列(ID,日期,架构版本,XMLData)定义一个表。这将轻松处理架构更改。但是您必须处理xml,这可能会为数据检索带来一定程度的复杂性。



By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.