数据库版本控制

3

我曾经做过一些项目(CMS和EC系统),需要对一些数据进行版本控制。

通常情况下,我会使用这样的模式:

+--------------+
+ foobar       +
+--------------+
+ foobar_id    +
+ version      +
+--------------+

它的效果非常好,但我想知道是否有更好的方法来实现。那种解决方案的主要问题是你必须始终使用子查询来获取最新版本。

例如:

SELECT * FROM foobar WHERE foobar_id = 2 and version = (SELECT MAX(version) FROM foobar f2 WHERE f2 = 2)

这会使大多数查询变得更加复杂,还会有一些性能缺陷。

因此,如果您分享创建版本化表的经验以及每种方法的优缺点,那将是很好的。

谢谢。


不是数据库专家,但你不能将该语句重构为存储过程吗? - RCIX
大概吧,但我大多数时候使用MySQL。 - RageZ
7个回答

5
我更喜欢将历史数据存储在另一个表中。我会创建“foobar_history”或类似的表,然后添加一个指向“foobar_id”的外键。这样可以避免使用子查询。它还有一个额外的好处,就是不会使您主要的数据表被大量历史数据污染,99%的情况下您不需要查看历史数据。
但是,您可能需要为更新数据创建触发器,因为它需要将当前数据复制到“_history”,然后再进行更新。

@jcm:谢谢JCM,是的,这种方法对我来说更好。虽然我需要重新编写很多代码,但没问题。谢谢。 - RageZ
我们这样做,并使用触发器在更新和删除时将当前记录存储到历史表中 - 历史表中还有一个附加列,用于指示是更新还是删除以及操作的日期/时间。请注意,我们不会将当前记录存储在历史表中(即没有插入触发器),我看到许多审计系统都这样做,但我认为这会使用大量磁盘空间来存储您已经在主表中拥有的数据。对于我们来说,将主ID + AuditDate作为历史表上的PK并不唯一,因此您可能还需要考虑一个IDENTITY列。 - Kristen
我唯一的疑问是你是否有一个版本列。如果每次编辑都会产生一个新版本,则历史记录表就可以了;如果用户/某个进程定义了何时达到新版本,则需要一些进一步的逻辑。你仍然可以有一个版本列,在历史记录中保存每个先前的更改,并查询具有给定版本号的最近历史记录记录 - 包括主表,以防请求当前版本的最新副本 :) - Kristen
是的,我同意Kristen的观点。当前记录不会进入历史表。在数据表中,您有一个可空的编辑器/更新时间,如果用户更改了值,则将填充该字段(它们可以在插入时填充,这取决于您的应用程序如何工作)。然后,当再次更改时,这些值将被推送到历史表中。 - Jim Mitchener
另外,关于Kristen提到的版本列,我同意这可能不是一个好主意。只需按更新时间排序,并将空值(如果适用)放在底部。如果需要,您可以在显示时生成“版本号”。 - Jim Mitchener

2
在我看来,最干净的解决方案是为每个需要版本控制的表单创建一个历史记录表。换句话说,有一个 foobar 表和一个 foobar_History 表,对 foobar 进行触发器操作,将现有数据写入带有时间戳和更改数据的用户的历史记录表中。旧数据可以轻松地进行查询,按时间戳降序排序,并且您知道主表中的数据始终是最新版本。

@baldy:是的,那很有道理,实际上会让很多事情变得更容易,比如我可以使用“INSERT INTO SELECT”来复制数据,而且我不必到处写“max”。好的,我们就这样做,我得重新编写很多代码,但我本应该先考虑一下的;-) - RageZ

2
我曾经在一个带有历史数据的系统上工作,我们有一个布尔值来指示哪个是数据的最新版本。当然,您需要在应用程序级别维护标志的一致性。然后,您可以创建使用标志的索引,如果在where子句中提供它,则速度很快。
优点:
- 易于理解 - 不需要对数据库模式进行重大更改 - 只需更新标志,无需将旧数据复制到另一个表中。
缺点:
- 标志需要在应用程序级别维护
否则,您可以依赖于单独的历史记录表,如几个答案所建议的那样。
优点:
- 将历史记录与实际数据分开清晰 - 可以在实体被删除时在数据库级别进行级联删除
缺点:
如果您想要完整的历史记录(即旧数据+当前数据),则需要2个查询(或一个联合查询)。 对于最新版本的数据对应的行将进行更新。据说根据改变的“大小”,更新比插入慢。 根据您的用例,最好的方法因情况而异。如果您需要处理真正的历史数据,则可能更好的选择是使用专用历史表。如果历史概念适用于您的领域模型,则可以在设计中处理它,否则您的数据库架构将与概念领域模型不同。如果在领域层面上,实际数据和旧数据需要以相同的方式处理,则拥有两个表会使设计变得复杂。
我还推荐M. Fowler的这篇文章,当涉及到处理时间数据时也很有趣:随时间变化的事物的模式

1

常见的技巧是为当前/过期添加一个名为version_status的列。还要注意,如果您将新记录和旧记录保存在同一张表中,您应该为实体拥有业务(自然)键,比如name + pin,因为主键会随着每行而变化(递增)。

TABLE foobar(foobar_id PK, business_key, version, version_status, .....)

SELECT * 
FROM foobar 
WHERE business_key = 'myFoobar3' AND version_status = 'current'

当决定将记录历史保存在同一张表中,还是将其移动到单独的表中时,请考虑其他具有foobar_id作为外键的表。在发布新版本时,现有的外键应该指向新的主键还是旧的主键?如果您想保留关系的历史记录,则可能希望将所有内容都保存在同一张表中。如果只有新版本很重要,则可以考虑将过期行移动到另一个表中--尽管这并不是必要的。


1

您可以通过使用视图来简化查询,该视图过滤到最新版本的表。这只是让查询看起来更好看,但仍然存在性能开销。


0
如果您使用Oracle,您可以使用分析函数。
选择* from( SELECT a.* ,row_number() over(partition by foobar_id order by version desc)rn 从foobar a WHERE foobar_id = 2 )其中rn = 1

0

这取决于您有多少表需要进行版本控制,以及您是否拥有事务性或报告系统。

如果只有几个事务性表格 - 只要性能问题不太显著,您现在的做法就可以。您可以添加一个当前行列和一个触发器来更新前一行,使其变为非当前行,从而使查询更容易。

但是,如果您有很多表格或额外的行正在减慢某些查询速度,那么我建议像其他人建议的那样使用历史表格以及历史触发器。请注意,您可以生成该代码以使开发和维护更加容易。

如果您处于报告领域,则还有许多其他选项,我在此不予讨论。您可以在数据仓库数据建模书籍中详细了解给出的选项。


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