数据库中的历史行管理

时间:2009-04-03 21:31:04

标签: database oracle database-design partitioning

与许多数据库一样,我正在设计一个数据库,该数据库应记录每个表中更改的行的先前版本。

此问题的标准解决方案是为每个数据表保留历史记录表, 每当需要在数据表中更新行时,当前行的副本将被插入到历史表中,而不是数据表中的行得到更新。

这个解决方案的缺点对我来说:

  • 维护2个表而不是1个(如果表的结构需要更改)
  • 应用程序需要知道两个表而不是一个
  • 表的名称可能需要很短,以保持表名和历史表名的约定(例如,SOME_TABLE,SOME_TABLE_HIST)

我正在考虑一个不同的解决方案,并想知道它是否正常。 对于每个表,我们添加列IS_LAST

  • 当一行插入到表中时,它将插入IS_LAST = 1。
  • 当一行更新时,原始行的副本将复制到同一个表中,并且IS_LAST = 0的更改,原始行将根据需要进行更新(仍然保持IS_LAST = 1)。

假设在我的情况下,行平均更新10次。 还假设应用程序执行的操作中至少有90%仅在最新版本的行上发生。

我的数据库是一个Oracle 10g,所以为了使“活动”表保持苗条,我们可以将表拆分为2个分区:IS_LAST = 1分区和IS_LAST = 0分区。

分区是解决历史数据保持问题的好方法吗?

此解决方案是否会限制这些表的其他分区潜力?

谢谢!

10 个答案:

答案 0 :(得分:6)

第一个问题应该是:你会对这些数据做些什么?如果您没有明确的业务要求,请不要这样做。

我做了类似的事情,经过3年的运行,大约有20%的“有效数据”,其余的是“以前的版本”。它有1000万+ 4000万条记录。在过去三年中,我们有两(2)次调查变更历史的请求,两次请求都是愚蠢的 - 我们记录了记录变更的时间戳,我们被要求检查人员是否加班(下午5点之后)。

现在,我们陷入了超大型数据库,其中包含80%无人需要的数据。

修改

既然你问过可能的解决方案,我会描述我们做了什么。它与您正在考虑的解决方案略有不同。

  1. 所有表都有代理主键。
  2. 所有主键都是从单个序列生成的。这很好,因为Oracle可以生成和缓存数字,因此这里没有性能问题。我们使用ORM,我们希望内存中的每个对象(以及数据库中的相应记录)都具有唯一标识符
  3. 我们使用ORM,数据库表和类之间的映射信息是属性的形式。
  4. 我们使用以下列记录单个存档表中的所有更改:

    • id(代理主键)
    • 时间戳
    • 原始表
    • 原始记录的ID
    • 用户ID
    • 交易类型(插入,更新,删除)
    • 将数据记录为varchar2字段
      • 这是字段名/值对形式的实际数据。

    事情就是这样:

    • ORM具有插入/更新和删除命令。
    • 我们为所有业务对象创建了一个基类,它覆盖了insert / update和delete命令
      • insert / update / delete命令使用反射以字段名/值对的形式创建字符串。代码查找映射信息并读取字段名称,关联值和字段类型。然后我们创建类似于JSON的东西(我们添加了一些修改)。当创建表示对象当前状态的字符串时,它将被插入到存档表中。
    • 当新的或更新的对象保存到数据库表时,它会保存到目标表中,同时我们将一个带有当前值的记录插入到归档表中。
    • 删除对象时,我们将其从目标表中删除,同时在存档表中插入一条记录,其中事务类型=“DELETE”

    临:

    • 我们没有数据库中每个表的归档表。架构更改时,我们也不必担心更新存档表。
    • 完整存档与“当前数据”分开,因此存档不会对数据库造成任何性能损失。我们将它放在单独的磁盘上的单独表空间中,它工作正常。
    • 我们创建了两个用于查看存档的表单:
      • 可根据存档表上的过滤器列出存档表的一般查看器。过滤数据用户可以在表单上输入(时间跨度,用户,...)。我们在表单fieldname / value中显示每条记录,每个更改都用颜色编码。用户可以查看每条记录的所有版本,他们可以查看更改的人员和时间。
      • 发票查看器 - 这个很复杂,但我们创建的表单显示的发票与原始发票输入表格非常相似,但有一些额外的按钮可以显示不同的代。创建此表单需要付出相当大的努力。表格被使用了几次然后被遗忘,因为在当前的工作流程中不需要它。
    • 用于创建存档记录的代码位于单个C#类中。数据库中的每个表都不需要触发器。
    • 表现非常好。在高峰时段,系统被大约700-800个用户使用。这是ASP.Net应用程序。 ASP.Net和Oracle都在一个带有8Gb RAM的双XEON上运行。

    缺点:

    • 单表归档格式比每个数据表都有一个归档表的解决方案更难阅读。
    • 搜索归档表中的非id字段很难 - 我们只能在字符串上使用LIKE运算符。

    所以,再次,检查存档上的要求。这不是一项微不足道的任务,但收益和使用可以是最小的。

答案 1 :(得分:2)

我创建了两个表:一个用于IsLast类值,一个用于历史值。然后我会设置一个触发器,每次更新isLast时都会将值插入历史表中。

答案 2 :(得分:1)

如果我有1或2个历史表保留,我会像Tuinstoel建议的那样完成。但如果你有几十个表来做这件事,我会更倾向于zendar描述的解决方案。原因是这样的。

你如何回答像

这样的问题
  • 自昨天一切正常以来发生了什么变化?

  • 用户SMITHG是否进行了任何更改?

这些问题需要每个表一个查询,无论是单独的_hist表还是表中的分区。无论如何,这是一些巨大的查询列表。如果你有一个看起来像这样的中央表,那么它就是一块馅饼。

table_name, Column_name, PK, Before_value, After_value, User, timestamp

插入仅在值之后

删除仅在值之前,

更新只有改变的列。

一些变化

如果您愿意,可以为I / U / D添加一列 您可以排除插入的列值,只记录PK和I,因为正确的值仍在表中。

由于这是Oracle,你可以在table_name上进行分区,所以实际上你实际上每个真实表都有一个hist“table”。

您可以轻松回答上述问题,我相信这些问题非常简单,是最常见的问题。它可以处理您可以使用分区或_hist表回答的每个问题。

答案 3 :(得分:1)

由于您使用的是Oracle,因此可以查看Oracle Flashback Technology。它记录数据库中所有更改的更改,包括数据和结构。它还记录时间戳和用户名。

我没有使用它,但它看起来很有能力。

答案 4 :(得分:0)

我想到的主要限制是,您的表的很大一部分将是历史数据,这意味着索引问题并可能在CRUD查询中引入额外的复杂性。

是否有一些特殊原因你不想使用通常解决这种情况的方法?

答案 5 :(得分:0)

您将如何定义主键?由于在同一个表中连接历史记录行,因此将有许多行具有相同的主键。

当单个“真实”行更改一次时,您似乎无法知道历史记录行的顺序。

(我参与过的一个项目,我们使用codesmith生成了所有历史表和触发器,这非常有用。)

答案 6 :(得分:0)

我会使用IS_LAST=1分区和IS_LAST=0分区系统。因为它是分区的,所以它会很快(分区修剪),你将永远不必查询普通表和历史表的并集。

我会使用IS_LAST ='Y'/'N'而不是1/0。 1和0毫无意义。

有一个特殊的技巧可以帮助保证每个实体最多有一行IS_LAST='Y':您可以创建一个基于函数的唯一索引,其函数在IS_LAST='N'时返回null并返回IS_LAST='Y'时的ID这里解释:http://www.akadia.com/services/ora_function_based_index_1.html

答案 7 :(得分:0)

根据时间跟踪它会帮助您在日常工作结束时或在工作结束时或在工作结束时或在工作结束时获得效果,具体取决于执行将尾随数据移入的过程的最低交易量时间那么历史表会有帮助吗?这样你所有的更新都将是插入,也不需要锁定。此致,安迪

答案 8 :(得分:0)

这完全取决于你拥有的东西:

  • 您运行的是标准版还是企业版?分区仅作为Enterprise Edition上的选项包含在内。关于here
  • 的更多信息
  • 如果您正在寻找一个不需要维护自己代码的简单解决方案,那么您可以考虑使用Workspace Manager来执行此操作。但是,我发现有一些限制(例如,Oracle Text索引维护似乎很难,如果不是不可能的话,尽管我只在10gR2上查看过它。)
  • 否则,我会选择zvolkov的解决方案(带有触发器写入历史表的实时表)或Mark Brady的解决方案(更改日志)。我使用了这两种模式,每种都有其优点和缺点。
  • @zendar:闪回查询仅适用于您撤消的时间。这不是一个长期解决方案,只是一个最多可以回顾几个小时的解决方案(取决于您指定的撤消保留多少)。

答案 9 :(得分:0)

与其他人一样,我使用ORM(Propel)和一个包含自定义保存的基础对象。删除方法。这些方法优先于标准保存和放大。删除ORM附带的内容。他们检查哪些列已更改,并在更改表中为每个更改的列创建1行。

change表的架构: change_pk,user_fk,user_name,session_id,ip_address,method,table_name,row_fk,field_name,field_value,most_recent,date_time

实施例: 1,4232,'Gnarls Barkley','f2ff3f8822ff23','234.432.324.694','更新','用户',4232,'first_name','Gnarles','Y','2009-08-20 10:10 :10' ;