【问题标题】:Database design for point in time "snapshot" of data?数据的时间点“快照”的数据库设计?
【发布时间】:2009-04-06 09:57:50
【问题描述】:

如何设计一个支持允许应用程序用户在某个时间点创建其数据快照的功能的数据库,有点像版本控制。

这将使用户能够返回并查看他们的数据过去的样子。

假设被“快照”的数据很复杂,包括多个表的连接。

我正在寻找一种方法,让每个应用程序用户能够对其数据进行快照并返回到该数据。整个数据库快照不是我想要的。

编辑:感谢您的回答。 6NF 的答案是令人信服的,由于其简单性,建议对快照数据进行反规范化。

澄清:这不是数据仓库的问题,也不是数据库备份和恢复的问题;它是关于如何构建一个模式,使我们能够在某个时间点捕获一组特定相关数据的状态。快照由应用程序用户在他们认为合适时生成。用户不会对整个数据库进行快照,只对他们感兴趣的数据对象进行快照。

【问题讨论】:

  • SQL Server 2005 或 2008。但是,如果有另一个 RDBMS 有针​​对此类问题的内置解决方案,我很想听听。谢谢
  • postgresq 上是否有任何扩展来对表进行版本控制?

标签: database-design


【解决方案1】:

这并不容易。

您实际上是在要求一个时间数据库(Christopher Date 称之为第六范式,或 6NF)。

要成为 6NF,模式也必须是 5NF,,基本上,对于每个数据,您需要附加一个时间范围,该值的数据适用于该时间范围。然后在连接中,连接必须只包括在考虑的时间范围内的行。

时间建模很难——这是第 6 范式解决的问题——并且在当前的 RDBMS 中没有得到很好的支持。

问题在于粒度。第 6 范式(据我所知)通过使每个非键(非键:,即任何“在”实体上的任何东西都可以在不丢失其身份的情况下改变)一个单独的关系来支持时间建模。为此,您添加时间戳或时间范围或版本号。将所有内容都连接起来解决了粒度问题,但这也意味着您的查询将变得更加复杂和缓慢。它还需要弄清楚所有键和非键属性;这往往是一项巨大的努力。

基本上,在您有关系的任何地方(“ted 拥有 id 为 789 的 GM 股票证书”),您都添加一个时间:“ted 拥有 id 为 789 的 GM 股票证书现在”,这样您可以同时说,“从 2000 年 2 月 3 日到昨天,fred 拥有 id 为 789 的 GM 股票证书”。显然,这些关系是多对多的,(ted 现在可以拥有多个证书,在他的一生中也可以拥有多个证书,fred 之前可以拥有 jack 现在拥有的证书)。

所以我们有一个所有者表,一个股票证书表,以及一个通过 id 关联所有者和证书的多对多表。在多对多表中,我们添加了一个 start_date 和一个 end_date。

现在,假设每个州/省/土地都对股票的股息征税,因此出于税收目的,要记录股票所有者的居住州。

所有者居住地明显可以随股权独立改变; ted 可以住在内布拉斯加州,购买 10 股,获得内布拉斯加州征税的股息,搬到内华达州,将 5 股卖给弗雷德,再购买 10 股。

但对我们来说,在某个时候可以搬到内布拉斯加州,在某个时候购买 10 股,在某个时候获得股息>,内布拉斯加州征税,在某个时候搬到内维达,在某个时候将 5 股卖给 fred,在某个时候再购买 10 股.

如果我们想计算 ted 在内布拉斯加州和内华达州欠的税款,我们需要所有这些,并在 person_stockcertificate 和 person_address 中加入匹配/重叠的日期范围。一个人的地址不再是一对一的,而是一对多的,因为它是时间范围内的地址。

如果 ted 购买了 10 股,我们是用一个购买日期模拟购买事件,还是为每股添加 date_bought?取决于我们需要模型回答的问题。

【讨论】:

  • 通用解决方案肯定很难实现,但对于足够具体的用例和数据子集,我描述的解决方案是可以接受的。
【解决方案2】:

我们通过创建单独的数据库表来做到这一点,其中包含我们想要快照的数据,但非规范化,即每条记录都包含有意义所需的所有数据,而不是对可能存在或可能不再存在的 id 的引用。它还为每一行添加了一个日期。

然后我们为特定的插入或更新生成触发器,对所有受影响的表进行连接,并将其插入到快照表中。

通过这种方式,编写将用户数据恢复到某个时间点的内容将是微不足道的。

如果你有一张桌子:

用户:

id, firstname, lastname, department_id

部门:

id, name, departmenthead_id

您的用户表快照可能如下所示:

user_id, user_firstname, user_lastname, department_id, department_name, deparmenthead_id, deparmenthead_firstname, departmenthead_lastname, snapshot_date

还有一个类似的查询

INSERT INTO usersnapshot
SELECT user.id AS user_id, user.firstname AS user_firstname, user.lastname AS user_lastname,
department.id AS department_id, department.name AS department_name
departmenthead.id AS departmenthead_id, departmenthead.firstname AS departmenthead_firstname, departmenthead.lastname AS departmenthead_lastname,
GETDATE() AS snapshot_date
FROM user
INNER JOIN department ON user.department_id = department.id
INNER JOIN user departmenthead ON department.departmenthead_id = departmenthead.id

这可确保快照中的每一行在该时间点都是正确的,即使在此期间部门或部门负责人发生了变化。

【讨论】:

  • 这让我想起了导出 CSV,除了输出存储在数据库中。
【解决方案3】:

拥有快照和/或审计跟踪是常见的数据库要求。对于许多应用程序,创建“影子”或审计表是一项简单而直接的任务。虽然数据库级备份和事务日志很好,但它们不是版本控制系统。

基本上,您需要创建一个与基表具有所有相同列的影子表,然后在基表上设置触发器,以便在更新或删除影子表时将行的副本放置在影子表中。

通过一些逻辑,您可以重新创建数据在给定时间点的样子。有关在 Sybase 中进行设置的简单方法,请参阅:http://www.theeggeadventure.com/wikimedia/index.php/Sybase_Tips#create_.27audit.27_columns

如果您需要做很多历史快照,那么您可以将数据保存在同一张表中。基本上,创建两列 - 添加和删除的列。缺点是每个查询都必须添加 where 子句。当然,您可以创建一个仅显示活动记录的视图。如果您有一个包含多个表的规范化数据库,这会变得有点复杂,所有表都带有历史记录。

但是,它确实有效。您只需在每个表上添加“添加”和“删除”列,然后您的查询就有感兴趣的时间点。每当修改数据时,您必须复制当前行,并将其标记为已删除。

【讨论】:

    【解决方案4】:

    使用Log Triggers

    所有数据更改都会被捕获,从而能够像在任何时间点一样进行查询。

    【讨论】:

      【解决方案5】:

      SQL Server 2005(以后)企业版可以创建Database snapshots

      【讨论】:

        【解决方案6】:

        Oracle 9i 版具有闪回技术,该技术在 Oracle 10g 和 11g 中得到了很大改进,只要您启用闪回,您就可以查看历史上任何给定点的数据库状态。

        查看此文档:Flashback Overview

        【讨论】:

          【解决方案7】:

          至少使用 SQL Server,您可以使用完整日志记录并在每个备份集之间保留事务日志。

          然后您可以进行时间点备份。

          这是一个糟糕的解决方案。

          您的客户到底想要什么?是否出于分析目的(即问题就像我们两周前有多少订单)?因为这正是数据仓库解决的问题。

          【讨论】:

            【解决方案8】:

            您可以使用 RDBMS 生成的日志来获取数据的快照。通常,日志用于提供数据库恢复。然而,它们也可用于跨多个 RDBMS 实例复制数据或获取数据的快照。

            要获取数据的快照,只需考虑在所需时间之前生成的所有日志。然后,您“回放”这些日志以获取恢复数据的实际数据库。

            如何访问和“回放”日志取决于您使用的具体 RDBMS 产品。

            另一种可能性是使用时态数据库。它们具有内置的时间方面并允许“回到过去”。例如,查找“Oracle 闪回技术”。 http://www.stanford.edu/dept/itss/docs/oracle/10g/appdev.101/b10795/adfns_fl.htm#ADFNS1008

            【讨论】:

              【解决方案9】:

              也许考虑使用像 MongoDB 这样的 NoSql 解决方案将所有关系数据聚合到一个文档中,然后将该文档与时间戳或版本号一起存储。 Kafka-Connect 或 Oracle Golden Gate 等解决方案简化了将关系数据传输到 NoSql 存储的过程。

              【讨论】:

                【解决方案10】:

                我会在每个表上使用一个额外的时间戳字段,无论是主表还是事实/事务表。即使目标是更新,也需要新插入所有表。

                【讨论】:

                • 查看How to Answer 和投票箭头鼠标悬停文本。这对 10 年前对一个高票数问题的高票数答案没有任何用处。
                猜你喜欢
                • 2013-04-07
                • 1970-01-01
                • 2019-11-13
                • 2011-07-20
                • 1970-01-01
                • 1970-01-01
                • 2019-04-28
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多