版本控制数据库的内容


16

我正在开发一个涉及用户可编辑内容的Web项目,并且我希望能够对存在于数据库中的实际内容进行版本跟踪。基本上,我想实现Wiki样式的更改历史记录。

做一些背景研究,我看到了很多有关如何对数据库模式进行版本控制的文档(实际上已经控制了我的数据库),但是关于模式跟踪数据库内容变化的任何现有策略都至少在模式版本控制方面大失所望。在我的搜索中。

我可以想到几种实现我自己的变更跟踪的方法,但它们似乎都非常粗糙:

  • 保存每次更改的整个行,并使用主键将行与源ID相关联(这是我目前最倾向于的方法,这是最简单的方法)。但是,许多小的更改可能会导致很多桌子膨胀。
  • 保存每个更改之前/之后/用户/时间戳记,并使用列名称将更改关联回相关列。
  • 用每列的表保存之前/之后/用户/时间戳(会导致表过多)。
  • 使用列将每个更改的差异/用户/时间戳记保存起来(这意味着您必须遍历整个干预更改历史记录才能返回到特定日期)。

最好的方法是什么?自己动手似乎是在重塑别人的(更好)代码库。


PostgreSQL的加分点。


这个问题已经在SO上讨论过:stackoverflow.com/questions/3874199/…。Google提供了“数据库记录历史记录”,您将找到更多文章。
Doc Brown

1
听起来像是事件搜索
James

为什么不使用SQL Server的事务日志来解决问题?
Thomas Junk

Answers:


11

我通常使用的技术是使用end_timestamp字段保存完整的记录。有一个业务规则,只有一行可以有一个空的end_timestamp,这当然是当前活动的内容。

如果采用此系统,强烈建议您添加索引或约束以强制执行规则。对于Oracle,这很容易,因为唯一索引可以包含一个且只能包含一个null。其他数据库可能更成问题。让数据库强制执行规则将使您的代码保持诚实。

您完全正确的是,许多小的更改都会导致膨胀,但是您需要权衡此点,以牺牲代码和报告的简便性。


请注意,其他数据库引擎的行为可能有所不同,例如,MySQL允许具有唯一索引的列中包含多个NULL值。这使该约束难以实施。
qbd 2015年

使用实际时间戳是不安全的,但是某些MVCC数据库通过将最小和最大事务序列号与元组一起存储在内部工作。
user2313838

“对于Oracle,这很容易,因为唯一索引可以包含一个且只能包含一个null”。错误。Oracle根本不在索引中包含空值。具有唯一索引的列中的空值数量没有限制。
Gerrat

@Gerrat自从我设计了一个具有此要求的数据库以来已有很多年了,我再也无法访问该数据库了。您是正确的,一个标准的唯一索引可以支持多个空值,但是我认为我们使用了唯一约束或功能索引。
kiwiron '18

8

请注意,如果您使用Microsoft SQL Server,则该功能已经具有“ 更改数据捕获”功能。您仍然需要编写代码才能访问在以后以前的修订版(CDC为此创建特定的视图),但是至少您不必更改表的架构,也不必实现更改跟踪本身。

在幕后,发生的事情是:

  • CDC会创建一个包含修订的附加表,

  • 您的原始表格会像以前一样使用,也就是说,任何更新都会直接反映在该表格中,

  • CDC表仅存储更改后的值,这意味着将数据重复保持在最低水平。

更改存储在不同表中的事实有两个主要结果:

  • 从原始表中进行选择的速度与没有CDC时一样快。如果我没记错的话,CDC会更新发生,因此更新速度同样快(尽管我不太记得CDC如何管理数据一致性)。

  • 对原始表的架构进行一些更改会导致CDC删除。例如,如果添加一列,CDC则不知道如何处理。另一方面,添加索引或约束应该没问题。如果在经常更改的表上启用CDC,这很快就会成为问题。可能存在一种解决方案,可以在不丢失CDC的情况下更改架构,但是我没有进行搜索。


6

首先以代码方式“从哲学上”解决问题。然后与代码和数据库进行“协商”,以实现它。

例如,如果您要处理一般性文章,则文章的初始概念可能如下所示:

class Article {
  public Int32 Id;
  public String Body;
}

在下一个最基本的级别上,我想保留一个修订列表:

class Article {
  public Int32 Id;
  public String Body;
  public List<String> Revisions;
}

我可能已经意识到当前的机构只是最新的修订版。这意味着两件事:我需要为每个修订版标上日期或编号:

class Revision {
  public Int32 Id;
  public Article ParentArticle;
  public DateTime Created;
  public String Body;
}

并且...和本文的当前正文不需要与最新修订版不同:

class Article {
  public Int32 Id;
  public String Body {
    get {
      return (Revisions.OrderByDesc(r => r.Created))[0];
    }
    set {
      Revisions.Add(new Revision(value));
    }
  }
  public List<Revision> Revisions;
}

缺少一些细节;但这说明您可能想要两个实体。一个代表文章(或其他标头类型),另一个代表修订版本列表(将任何具有良好“哲学”意义的字段归为一组)。最初,您不需要特殊的数据库约束,因为您的代码本身并不关心任何修订-它们是了解修订的文章的属性。

因此,您不必担心以任何特殊方式标记修订或依靠数据库约束来标记“当前”文章。您只需为其打上时间戳(即使是自动添加的ID也可以),使其与其父文章相关,然后让该文章负责了解“最新”文章是最相关的文章。

然后,您可以让ORM处理较少的哲学细节,或者,如果您不使用现成的ORM,则可以将它们隐藏在自定义实用程序类中。

不久之后,在进行了一些压力测试之后,您可能会考虑使该修订版属性延迟加载,或者使您的Body属性仅延迟加载最高版本。但是,在这种情况下,您的数据结构不必更改以适应这些优化。


2

PostgreSQL Wiki页面上有一个审计跟踪触发器它会引导您完成如何设置将满足您需要的审核日志。

它跟踪更改的完整原始数据,以及更新的新值列表(对于插入和删除,只有一个值)。如果要还原旧版本,则可以从审核记录中获取原始数据的副本。请注意,如果您的数据涉及外键,则可能还必须回滚那些记录以保持一致性。

一般而言,如果您的数据库应用程序大部分时间都花在仅当前数据上,那么我认为最好不要在与当前数据相同的表中跟踪备用版本。这将使您的活动表索引更易于管理。

如果要跟踪的行很大并且空间非常重要,则可以尝试分解更改并存储最小的差异/补丁,但这绝对是覆盖所有数据类型的工作。我之前已经做过,通过一次向后浏览所有更改来重建旧版本的数据很痛苦。


1

好吧,我最后只是选择了最简单的选项,该触发器将行的旧版本复制到每个表的历史日志中。

如果我对数据库的负担过大,可以视需要折叠一些较小的历史记录更改。

由于我想自动生成触发函数,因此解决方案变得非常混乱。我是SQLAlchemy,所以我可以通过做一些继承hikinks来生成历史表,这很好,但是实际的触发器函数需要进行一些字符串调整才能正确生成PostgreSQL函数,并将列从一个表映射到另一个正确。

不管怎么说,这一切都在github 这里

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.