【问题标题】:Normalize or denormalize for storing revision histories in a RDBMS?规范化或非规范化以在 RDBMS 中存储修订历史?
【发布时间】:2012-04-24 03:14:10
【问题描述】:

我有一个基本的 CRUD 网络应用程序,人们可以在其中创建/编辑文章。我现在想添加保留所有编辑的修订历史的功能。目前,我有一个 Articles 表,如下所示:

Article(id, title, content, author_id, category_id, format)

我考虑了 2 个选项来更改我当前的架构以添加对修订历史的支持。基本思想是将任何文章的每一次编辑都存储为修订表中的记录。所以 Articles 和 Revisions 是一对多的关系。

第一个选项(标准化): 一张表用于文章元数据,一张用于修订。没有存储重复数据。

Article(id, title, category_id)
Revision(id, content, author_id, format)

第二个选项(去规范化): 两个表与选项 1 类似,但有一些重复的列。

Article(id, title, content, author_id, category_id, format)
Revision(id, article_id, content, author_id, format)

我正在考虑使用第二个选项,因为它会使我的编码更容易(不那么复杂,代码行数更少)。我知道这不是“学术的”和“纯粹的”,但我个人的感觉是,必须进行额外的连接会损害代码维护。此外,性能应该会更好,因为不需要进行那么多的连接。

这是完成这项任务的好方法吗?我可能忽略了任何不可预见的或长期的后果?

【问题讨论】:

  • JNK 是正确的(尽管 SE 中的 SQL 没有针对连接进行优化 - RDBMS 是。虽然细节)。我们的发票应用程序也有类似的问题,但“历史”表是发票表的精确副本,还有一些附加字段(历史 PK、时间戳等)。易INSERT INTO HISTORY SELECT NULL,NOW(),...,i.* from invoices i

标签: sql database-design schema rdbms


【解决方案1】:

如果您关心您的数据,那么在“非规范化”情况下,您将不会得到更少的代码 - 您必须强制 Revision 中的最新行始终与 Article 中的副本匹配。这在并发环境中实际上远非微不足道 - 您必须非常小心地进行锁定!

(如果您选择 RevisionArticle 不包含相同的副本,那么情况会更糟 - 您将无法依赖 DBMS 来强制执行 Revision 主键!)

有了足够强大的 DBMS,您就可以大吃一惊了 - 例如,Oracle 物化视图可以为您“预先加入”数据,而无需对实际数据模型进行非规范化处理。

即使您没有这样的 DBMS,也只有在您测量真实数据量的性能后才考虑进行非规范化。是的,JOINS 可能很昂贵,但在您的特定情况下它们是否昂贵?只有测量才能说明问题。


顺便说一句,考虑像这样使用识别关系/自然键:

revision_no 在给定文章下添加修订时单调增长。

Revision PK 下的 B-Tree 结构使得查找给定文章的最新(或任何!)修订版变得非常高效。除非您的问题中未显示备用键,否则您还可以 cluster Revision 和(在 Oracle 下)甚至压缩集群索引的前沿,因此重复 article_id 的空间开销被取消。

【讨论】:

  • 我从您的评论中学到了很多,我将使用标准化选项。我选择另一个答案作为“解决方案”,因为他的建议是我最终使用的。
  • @trinth 小心Article.CurrentRevision。据推测,Revision 已经在某个字段上排序,并且可以从该顺序自然地推断出最后一次修订。因此,CurrentRevision 不会向系统引入任何新信息,它只是复制现有信息 - 它是冗余,并且冗余会导致修改异常。您甚至没有从它的存在中获得任何性能优势(在 B 树中,搜索 MAX 与搜索具体值一样快)。只有当“最后”和“当前”版本的含义不同时,它的存在才是合理的。
【解决方案2】:

性能论点是无稽之谈 - 你做得更少JOINs,但 RDBMS 已针对JOINs 进行了优化。

但是,您可能会从服务器中提取 很多 超出必要的数据,这些数据无法优化。

您还可能遇到一致性问题。在不同表中复制同一项目的数据会导致出现不一致的情况。如果修订记录和文章记录具有不同的formatauthor 值怎么办?你怎么知道哪个是正确的?如果Articles 中的content 与任何修订版都不匹配怎么办?

你真的应该把它标准化。我会在您的 Articles 表中添加一个 CurrentRevision 字段以链接到当前版本,并且您应该在 Revisions 表中有一个 ArticleID 以将两者链接在一起。

【讨论】:

  • 感谢您阐明这一点。我现在意识到保持一致性的代码最终可能需要更多的工作..
  • CurrentRevision 字段真的有必要吗?这意味着每次创建或编辑文章时对数据库进行 3 次调用:1. 创建文章 2. 参考步骤 (1) 中的文章创建修订版 3. 使用步骤 (2) 中的修订版更新 article.current_revision
  • @trinth 不过,没有理由所有这些都需要单独调用。您可以一次调用插入文章和参考,您只需正确处理代码中的 ID 值即可。
猜你喜欢
  • 2013-08-21
  • 2015-09-09
  • 2021-05-29
  • 2016-05-13
  • 2018-06-06
  • 1970-01-01
  • 2013-01-18
  • 2013-03-29
  • 2021-06-13
相关资源
最近更新 更多