存储修订时避免循环依赖?

时间:2013-05-18 00:53:51

标签: mysql database-design circular-dependency

以下是我必须存储网页的两个表的简单说明:

Page
----
* PageId
CurrentRevisions -> PageRevisions.RevId

PageRevisions
-------------
* RevId
PageId -> Page.PageId
Title
Contents

这背后的想法是我可以在PageRevisions中存储多个页面修订,而页面只是一个ID和对特定页面修订的引用。

显然,页面只能将一个修订版本称为“当前”版本,而许多修订版本可以引用回单个页面。

问题在于这是一种循环关系。在MySQL中,在强制使用外键的情况下,如果没有首先创建PageRevision,我就无法创建Page,如果没有先创建Page,我就无法创建PageRevision。

我可以删除Page.CurrentRevisions并添加PageRevisions.isCurrent,但我不喜欢这个设计会允许将一个以上的版本标记为当前 - 我希望数据库设计强制执行该约束(没有触发器)。

2 个答案:

答案 0 :(得分:3)

你正在寻找的东西被称为“延迟约束”,虽然它在PostgreSQL和Oracle等一些数据库系统中得到支持,但它并不在MySQL中(据我所知)。它基本上意味着在每个INSERTUPDATE语句中不会检查外键约束之类的关系,但只有在整个事务提交时才会检查,因此您可以在中间时间内自由地违反它们只要你在完成之前清理乱七八糟的阶段。

在你的鞋子里,我可能会建议让CurrentRevisions可以为空。然后,您可以使用空当前版本创建占位符Page,创建引用占位符页面的PageRevision,然后在两个记录都在数据库中之后设置当前版本。您必须依靠业务逻辑来强制执行一致性并确保每个页面都有当前版本,但这不是世界末日。

答案 1 :(得分:2)

Jeremy Todd提供了一个good answer(给他+1),我只是想补充一点......

“当前”是否与“最新”相同?如果是,那么您可以使用标识关系和生成的复合键来自然地建模:

enter image description here

同一页面的所有修订都具有相同的PageRevision.PageId,并且页面中的历史顺序由整数RevNo确定。最新版本只是其各自页面中RevNo最高的版本。

InnoDB tables are clustered开始,此结构会将同一页面的修订版本组合在一起。检索页面的所有修订版本可能比原始结构快得多,并且只检索最新修订版本的速度快。

数据修改也会更快,因为我们只有一个索引。


  

我可以删除Page.CurrentRevisions并添加PageRevisions.isCurrent,但我不喜欢这个设计允许将一个以上的版本标记为当前

不是我推荐这种方法,但是“当前”标志的唯一性可以通过声明方式强制执行,只需使用NULL而不是false:

CREATE TABLE PageRevision (
  RevId INT PRIMARY KEY,
  PageId INT NOT NULL,
  IsCurrent BIT CHECK (IsCurrent IS NULL OR (IsCurrent IS NOT NULL AND IsCurrent = 1)),
  UNIQUE (PageId, IsCurrent)
);

-- You can insert several "non current" revisions for the same page.
INSERT INTO PageRevision VALUES (1, 1, NULL);
INSERT INTO PageRevision VALUES (2, 1, NULL);
INSERT INTO PageRevision VALUES (3, 1, NULL);

-- You can insert one "current" revision in one page.
INSERT INTO PageRevision VALUES (4, 1, 1);

-- Or another "current" revision in a different page.
INSERT INTO PageRevision VALUES (5, 2, 1);

-- But not the second "current" revision in the same page.
-- The following violates the UNIQUE constraint:
INSERT INTO PageRevision VALUES (6, 1, 1);

注意:MySQL解析但不强制执行上面的CHECK约束。因此,除了每页一个(有用的)真实标记外,每页还可能有一个(不需要的)错误标记。

注意2:由于peculiar nature of NULL,上面的CHECK可以简单地重写为:CHECK (IsCurrent = 1)。当标志为0时,表达式为false,并且CHECK按预期失败。如果该标志为1,则表达式为true且CHECK通过。如果该标志为NULL,则表达式为NULL,并且CHECK 传递(与将NULL视为false的WHERE不同)。但是我更喜欢比处理NULL时更明确。