如何存储历史数据[关闭]

Posted

技术标签:

【中文标题】如何存储历史数据[关闭]【英文标题】:How to Store Historical Data [closed] 【发布时间】:2011-04-21 21:53:43 【问题描述】:

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

【问题讨论】:

重复***.com/questions/323065/… 【参考方案1】:

SQL Server 2016 及更高版本 中,有一个名为Temporal Tables 的新功能旨在以开发人员的最小努力 解决这一挑战。时态表的概念类似于变更数据捕获 (CDC),不同之处在于,时态表抽象了您在使用 CDC 时必须手动执行的大部分操作。

【讨论】:

【参考方案2】:

只是想添加一个我开始使用的选项,因为我使用的是 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了解到这个

【讨论】:

【参考方案3】:

您可以在表上创建物化/索引视图。根据您的要求,您可以对视图进行全部或部分更新。请参阅此内容以创建 mview 和日志。 How to create materialized views in SQL Server?

【讨论】:

【参考方案4】:

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

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

【讨论】:

【参考方案5】:

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

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

存档/历史:对于跟踪以前的地址、电话号码等情况。如果您的活动事务表架构在未来没有显着变化(如果您的历史表必须具有相同的结构)。 如果您预计表规范化、数据类型更改添加/删除列,请将您的历史数据存储为 xml 格式。定义具有以下列(ID、日期、架构版本、XMLData)的表。这将轻松处理架构更改。但是您必须处理 xml,这可能会给数据检索带来一定程度的复杂性。

【讨论】:

【参考方案6】:

另一种选择是按[每天|每小时|随便]归档操作数据。大多数数据库引擎support the extraction of the data into an archive。

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

    确定操作数据库中的当前表 从每个表中选择所有数据到 CSV 或 XML 文件中 将导出的数据压缩为 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

【讨论】:

这根本不适合历史数据,因为如果有人更改了一个值并在存档周期内将其更改回,则该更新将丢失。也没有简单的方法来查看一个实体随时间的变化或部分恢复一个实体。【参考方案7】:

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

【讨论】:

在几个历史报告中JOIN 两个表更难,还是更难修改每个表插入/更新/删除以了解历史问题?实际上,审计日志甚至会在历史表中包含当前数据,因此报告中甚至不需要当前表。【参考方案8】:

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

如果您尽快尝试其他方法,您将面临问题:

维护开销 选择中的更多标志 查询速度变慢 表、索引的增长

【讨论】:

【参考方案9】:

你可以只对表进行分区吗?

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

【讨论】:

是的,我可以对表进行分区,但这是处理历史数据时的标准吗?历史数据是否应该与活动数据包含在同一个表中?这些是我想讨论的问题。这也不是任意的,因为它与 SQL Server 2008 有关。【参考方案10】:

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

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

主表 例如称为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”,如果愿意)。在明细表中创建了一个新行,仍然链接到同一个主控,状态控制为“C”,进行更新的人的 ID 和存储在 XMLDATA 列中的新数据。

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

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

【讨论】:

嗨,克里斯,如果您这样做,ID(主键)必须更改对吗?如果它被另一个表使用,与另一个表的关系如何? @projo 您的主表上的 ID 是 PK,并且在概念上是您正在处理的任何概念的“PK”。 detail 表上的 ID 是 PK,用于标识 master 的历史版本(这是 detail 上的另一列)。在建立关系时,您通常会参考概念的真实 PK(即主表上的 ID 或详细信息上的 MASTER_ID 列)并使用 STATUS_CONTROL = 'C' 来确保您获得的是当前版本。或者,您可以引用详细 ID 以将某些内容与特定时间点相关联。 我们使用相同的方法。但现在我想知道只存储 START_DATETIME 而不存储 END_DATETIME 是否更好 我的经验有几种变化。如果您的实体已“结束”,即存档或删除,那么您实际上可能没有具有“C”状态控制的详细记录,即没有当前行,尽管您不知道何时发生。或者,您可以在最后一行设置 end_datetime,并且存在“已结束”“C”行可能表明该实体现在已被删除/存档。最后,您可以通过另一列 STATUS 来表示这一点,您可能已经拥有了。 @ChrisCameron-Mills 当我们有一个主表和一个详细信息表时,您建议的方法会更好。如果详细信息表依赖于其他几个也随时间变化的表怎么办?一种可能的方法是添加类似的列来跟踪所有这些表的版本控制。但这不会太复杂吗?【参考方案11】:

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

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

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

历史报告:报告历史状态、“当前”位置或一段时间内的分析报告。通过查询上述类型的审计日志表,可以满足简单的历史报告要求。如果您有更复杂的需求,那么为报告实施数据集市可能比尝试将历史直接集成到操作系统中更经济。到目前为止,缓慢变化的维度是最简单的跟踪和查询历史状态和大部分历史跟踪可以自动化。通用处理程序并不难编写。通常,历史报告不必使用最新数据,因此批量刷新机制通常就可以了。这使您的核心和报告系统架构相对简单。

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

【讨论】:

我想我明白你在说什么。所以我对 FOO_Hist 表所做的实际上是创建一个审计表。我没有在更新时使用触发器插入审计表,而是在程序中运行了一条语句。对吗? 差不多。不过,最好使用触发器进行这种审计日志记录;触发器确保任何更改(包括手动数据修复)都记录在审计日志中。如果您有超过 10-20 个表要审核,那么构建触发器生成器工具可能会更快。如果审计日志的磁盘流量有问题,您可以将审计日志表放在一组单独的磁盘上。

以上是关于如何存储历史数据[关闭]的主要内容,如果未能解决你的问题,请参考以下文章

我如何将此值存储到数据库中[关闭]

如何将 laravel 日志文件数据存储到数据库中(5.5)[关闭]

如何将数据库值存储在字符串中? [关闭]

Android 的 Actionscript:如何在 sql 中存储“默认数据”?重点是啥? [关闭]

如何使用网站开放库将信息存储到数据库中[关闭]

如何将视频快速存储在核心数据中? [关闭]