9

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

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

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

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

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

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

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

我的数据库是 Oracle 10g,因此为了保持“活动”表的精简,我们可以将表拆分为 2 个分区:IS_LAST=1 分区和 IS_LAST=0 分区。

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

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

谢谢!

4

10 回答 10

6

第一个问题应该是:您将如何处理这些数据?如果您没有明确的业务需求,请不要这样做。

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

现在,我们被超大的数据库困住了,其中包含 80% 的数据,而这些数据没有人需要。

编辑:

既然您要求可能的解决方案,我将描述我们所做的。这与您正在考虑的解决方案有点不同。

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

我们用以下列记录单个存档表中的所有更改:

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

事情是这样工作的:

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

临:

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

缺点:

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

因此,再次检查存档的要求。这不是一项微不足道的任务,但收益和使用可能是微乎其微的。

于 2009-04-03T22:06:02.790 回答
2

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

于 2009-04-03T21:42:52.753 回答
1

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

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

于 2009-04-06T10:36:52.923 回答
1

如果我有 1 或 2 张历史表格要保留,我会完全按照 Tuinstoel 的建议进行操作。但是如果你有几十个表来做这件事,我会更倾向于 zendar 描述的解决方案。原因是这样的。

你如何回答这样的问题,

  • 自从昨天一切都好之后发生了什么变化?

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

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

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

插入只有 after 值,

删除只有之前的值,

更新两者都有,但仅适用于更改的列。

一些变化

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

由于这是 Oracle,您可以在 table_name 上进行分区,因此本质上您实际上每个真实表都有一个 hist“表”。

您可以轻松回答上述问题,我认为这些问题很简单,是最常被问到的问题。它可以处理您可以通过分区或 _hist 表回答的所有问题。

于 2009-04-04T14:28:08.500 回答
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'、'UPDATE'、'User'、4232、'first_name'、'Gnarles'、'Y'、'2009-08-20 10 :10:10';

于 2009-08-20T07:17:48.413 回答
0

这一切都取决于你有什么:

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

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

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

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

于 2009-04-04T09:56:28.120 回答
0

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

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

于 2009-04-03T21:43:54.403 回答
0

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

此外,当单个“真实”行再次更改时,您似乎没有办法知道历史记录行的顺序。

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

于 2009-04-03T21:51:12.920 回答
0

如果您执行了将跟踪数据移动到历史表中的过程,那么基于时间跟踪它是否可以帮助您在每天以及在业务结束时或在午夜根据最低交易量时间实现您正在寻找的效果这会有帮助吗 ??这样,您的所有更新都将被插入,并且也不需要锁定。问候,安迪

于 2009-04-06T04:21:23.480 回答