如何存储历史数据

时间:2010-10-06 15:32:08

标签: database-design

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

我的同事说这是糟糕的设计,因为出于历史原因我不应该有一个表的精确副本,而应该只是在活动表中插入另一条记录,并带有一个标志,表明它是出于历史目的。

是否有处理历史数据存储的标准?在我看来,我不想把我的所有历史记录混合在同一张表中,因为它可能超过一百万条记录(我在考虑长期)。

您或您的公司如何处理此事?

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

13 个答案:

答案 0 :(得分:71)

直接在操作系统中支持历史数据将使您的应用程序比原本复杂得多。一般来说,我不建议这样做,除非你有一个硬性要求操纵系统内记录的历史版本。

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

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

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

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

如果您的要求属于这两个类别中的一个,那么最好不要将历史数据存储在您的操作系统中。将历史功能分离到另一个子系统可能会减少整体工作量,并生成更符合其预期目的的事务和审计/报告数据库。

答案 1 :(得分:35)

我认为没有特定的标准方法,但我认为我会投入一种可能的方法。我在Oracle和我们的内部Web应用程序框架中工作,该框架利用XML来存储应用程序数据。

我们使用一种称为Master - Detail模型的东西,它最简单的包括:

主表,例如名为Widgets,通常只包含ID。通常包含不会随时间变化的数据/不是历史数据。

详细信息/历史记录表,例如名为Widget_Details,至少包含:

  • ID - 主键。详细/历史ID
  • MASTER_ID - 例如在这种情况下称为'WIDGET_ID',这是主记录的FK
  • START_DATETIME - 指示该数据库行开始的时间戳
  • END_DATETIME - 指示该数据库行结束的时间戳
  • STATUS_CONTROL - 单个char列指示行的状态。 'C'表示当前,NULL或'A'将是历史/存档的。我们只使用这个,因为我们无法在END_DATETIME上编入索引
  • 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')版本。

答案 2 :(得分:10)

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

如果你很快就尝试其他方法,你将面临问题:

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

答案 3 :(得分:2)

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

答案 4 :(得分:1)

这个问题相当陈旧,但人们仍然在研究这个问题。因此,如果您使用的是oracle,您可能会对oracle闪回感兴趣:http://docs.oracle.com/cd/B28359_01/appdev.111/b28424/adfns_flashback.htm

答案 5 :(得分:1)

更改数据捕获:https://docs.microsoft.com/en-us/sql/relational-databases/track-changes/about-change-data-capture-sql-server?view=sql-server-2017

它在SQL Server 2008 R2中受支持,在SQL Server 2008中可能受支持。

答案 6 :(得分:0)

您可以对表格进行分区吗?

“使用SQL Server 2008的分区表和索引策略” 当数据库表的大小增加到数百GB或更多时,加载新数据,删除旧数据和维护索引会变得更加困难。只是表的大小导致这样的操作需要更长的时间。即使是必须加载或删除的数据也可能非常大,因此对表进行INSERT和DELETE操作是不切实际的。 Microsoft SQL Server 2008数据库软件提供表分区,以使此类操作更易于管理。“

答案 7 :(得分:0)

真正的问题是,您是否需要将历史数据和活动数据一起用于报告?如果是这样,请将它们保存在一个表中,分区并创建活动记录的视图以在活动查询中使用。如果您只是偶尔需要查看它们(研究leagal问题或其他问题),请将它们放在一个单独的表中。

答案 8 :(得分:0)

另一种选择是以[每日|每小时]的方式存档运营数据。大多数数据库引擎support the extraction of the data into an archive

基本上,我们的想法是创建一个

的预定Windows或CRON作业
  1. 确定操作数据库中的当前表
  2. 从每个表中选择所有数据到CSV或XML文件
  3. 将导出的数据压缩为ZIP文件,最好使用文件名中生成的时间戳,以便于存档。
  4. 许多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
    

答案 9 :(得分:0)

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

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

存档/历史:对于跟踪以前的地址,电话号码等情况,如果您的活动交易表架构未来未发生重大变化(如果您的历史记录),则创建单独的表FOO_HIST会更好表必须具有相同的结构)。 如果您预期表规范化,数据类型更改列的添加/删除,则以xml格式存储历史数据。使用以下列定义一个表(ID,Date,Schema Version,XMLData)。这将很容易处理架构更改。但你必须处理xml,这可能会为数据检索带来一定程度的复杂性。

答案 10 :(得分:0)

您可以使用MSSQL Server审核功能。从SQL Server 2012版本开始,您将在所有版本中找到此功能:

http://technet.microsoft.com/en-us/library/cc280386.aspx

答案 11 :(得分:0)

您可以在表格上创建实体化/索引视图。根据您的要求,您可以完全或部分更新视图。请看这个创建mview和日志。 How to create materialized views in SQL Server?

答案 12 :(得分:0)

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

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

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

我通过此链接here

了解到了这一点