版本控制数据库持久化对象,你会怎么做?

45

(与版本控制数据库架构无关)

与数据库交互的应用程序通常具有由许多表中的数据组成的领域对象。假设该应用程序支持对这些领域对象进行版本控制(类似于CVS)。

对于某个任意的领域对象,您将如何设计数据库模式以处理此要求?是否有任何经验可以分享?


请查看这个现有的问题:https://dev59.com/xHVD5IYBdhLWcg3wQJOT - Matthew Watson
请参见 https://dev59.com/MHRC5IYBdhLWcg3wW_tk。 - Marco Eckstein
9个回答

23
仔细考虑修订要求。一旦您的代码库内置操作系统历史记录跟踪功能,它将变得非常复杂。保险 核保 系统 尤其糟糕,模式通常运行超过1000个表。查询也往往相当复杂,这可能导致性能问题。
如果历史状态确实只用于报告,请考虑实施一个“当前状态”事务系统,其数据仓库结构挂在后面以跟踪历史。缓慢更改维度 是一种比直接嵌入临时历史跟踪机制到操作系统中更简单的跟踪历史状态的结构。
此外,更改数据捕获 对于“当前状态”系统来说更简单,因为更改是在原地对记录进行的 - 记录的主键不会更改,因此您不必将持有同一实体不同版本的记录匹配在一起。有效的CDC机制将使增量仓库加载过程相当轻量级,并且可以频繁运行。如果您不需要最新的历史状态跟踪(几乎是一个矛盾),那么这可以是一种有效的解决方案,其代码库比直接构建应用程序的完整历史跟踪机制更简单。

刚刚阅读了SCD页面。有很多值得思考的内容。另一种Type 6看起来似乎与我密切相关。 - Chris Vest
类型2/6(类型6只是具有自连接到最新版本的类型2)维度可能是您在此处想要的。如果您想频繁更新仓库,请考虑使用更改数据捕获。 - ConcernedOfTunbridgeWells
我想说虽然这已经是两年前的事情了(几乎就是今天),但这个答案非常有用!谢谢! - corsiKa

14

过去我用过的一种技术是在数据库中有一个“世代”的概念,每次更改都会使数据库的当前世代号增加——如果你使用Subversion,请考虑版本。 每个记录都有两个与其关联的世代号(表中的2个额外列)-记录开始有效的世代和停止有效的世代。 如果数据当前有效,则第二个数字将为NULL或其他通用标记。

因此,要插入到数据库中:

  1. 增加世代号
  2. 插入数据
  3. 使用有效的from并将有效的to标记为NULL来标记该数据的生存期

如果您要更新某些数据:

  1. 将所有即将修改的数据标记为有效到当前世代号
  2. 增加世代号
  3. 使用当前世代号插入新数据

删除仅涉及将数据标记为在当前世代结束。

要获取特定版本的数据,请找到您想要的世代,并查找在那些世代版本之间有效的数据。

例子:

创建一个人。

|Name|D.O.B  |Telephone|From|To  |
|Fred|1 april|555-29384|1   |NULL|

更新电话号码。

|Name|D.O.B  |Telephone|From|To  |
|Fred|1 april|555-29384|1   |1   |
|Fred|1 april|555-43534|2   |NULL|

删除Fred:

|Name|D.O.B  |Telephone|From|To  |
|Fred|1 april|555-29384|1   |1   |
|Fred|1 april|555-43534|2   |2   |

3

严格版本控制的替代方案是将数据分为两个表:当前表和历史表。

当前表包含所有实时数据,并具有您构建的所有性能优势。任何更改都会首先将当前数据写入相关的“历史”表中,并附带一个日期标记,以指示更改时间。


2
如果您正在使用Hibernate, JBoss Envers可能是一个选择。您只需要在类上注释 @Audited 即可保留它们的历史记录。

1

你需要在一个主表中拥有一个主记录,其中包含所有版本共同的信息。

然后每个子表使用主记录ID + 版本号作为主键的一部分。

虽然可以不使用主表来完成,但根据我的经验,这往往会使SQL语句变得更加混乱。


0

一旦对象保存在数据库中,我们可以随意修改该对象的任意次数。如果我们想知道对象被修改的次数,则需要应用版本控制概念。

每当我们使用版本控制时,当对象第一次保存在数据库中时,Hibernate将版本号插入为零。稍后,当对该特定对象进行修改时,Hibernate会自动将版本号递增一次。 为了使用此版本控制概念,我们需要在应用程序中进行以下两个更改。

Add one property of type int in our pojo class.

In hibernate mapping file, add an element called version soon after id element

0

我不确定我们是否有相同的问题,但我需要对当前数据集进行大量“建议性”更改(包括链式建议,即建议上的建议)。

就像源代码控制中的分支一样,但是针对数据库表格。

我们还想要一个历史日志,但这是最不重要的因素——主要问题是管理变更建议,这些建议可能会挂起6个月或更长时间,因为业务部门正在考虑变更批准并准备实施实际变更。

这个想法是用户可以加载一个变更并开始创建、编辑、删除当前状态的数据,而不必实际应用这些变更。撤消他们可能做出的任何更改,或取消整个变更。

我能够实现这一点的唯一方法是在我的版本化表格上设置一组公共字段:

根ID:必填——当记录的第一个版本创建时,设置为主键。这代表所有时间的主键,并复制到记录的每个版本中。在命名关系列时,您应该考虑根ID(例如,PARENT_ROOT_ID而不是PARENT_ID)。由于根ID也是初始版本的主键,因此可以根据实际的主键创建外键——实际所需的行将由下面定义的版本过滤器确定。

变更ID:必填项 - 每个记录都通过变更来创建、更新、删除

复制自ID:可空 - null表示新创建的记录,非null表示在更新时从哪条记录ID克隆了该行

生效日期/时间:可空 - null表示建议的记录,非null表示记录何时变为当前记录。不幸的是,无法在根ID /生效时间上放置唯一索引,因为任何根ID都可以有多个空值(除非您想限制每个记录只有一个建议更改)

失效日期/时间:可空 - null表示当前/建议,非null表示何时成为历史性的。虽然从技术上讲它不是必需的,但能帮助加速查询查找当前数据。如果手动编辑此字段,可能会破坏它,但可以从“生效日期/时间”重建。

删除标记:布尔型 - 当建议将记录删除后将其设置为true以成为当前记录。当提交删除时,他们的" 失效日期/时间"被设置为与" 生效日期/时间"相同的值,使它们过滤出当前数据集。

按照变更获取数据的当前状态的查询应为;

SELECT * FROM table WHERE (CHANGE_ID IN :ChangeId OR (EFFECTIVE_FROM <= :Now AND (EFFECTIVE_TO IS NULL OR EFFECTIVE_TO > :Now) AND ROOT_ID NOT IN (SELECT ROOT_ID FROM table WHERE CHANGE_ID IN :ChangeId)))

(变化-变化倍数的过滤是在此查询之外完成的)。

获取某一时刻数据当前状态的查询将是;

SELECT * FROM table WHERE EFFECTIVE_FROM <= :Now AND (EFFECTIVE_TO IS NULL OR EFFECTIVE_TO > :Now)

常见的索引创建在 (ROOT_ID, EFFECTIVE_FROM), (EFFECTIVE_FROM, EFFECTIVE_TO) 和 (CHANGE_ID) 上。

如果有更好的解决方案,我很乐意听取建议。


0
一个简单而又不会出错的方法,是在您的数据表中添加一个版本列,并存储对象的版本号,然后根据该版本号选择相应的应用程序逻辑。这样做,您还可以以较小的成本获得向后兼容性,这总是很好的。

0

ZoDB + ZEO 实现了一个基于修订的数据库,支持完全回滚到任何时间点。快去看看吧。

不好的地方:它与 Zope 绑定。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接