1

以下是我要存储网页的两个表的一个非常简单的说明:

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

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

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

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

问题是这是一个循环关系。在 MySQL 中,在强制执行外键的情况下,如果不先创建 PageRevision,我将无法创建 Page,并且如果不先创建 Page,我将无法创建 PageRevision。

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

4

2 回答 2

3

您正在寻找的东西称为“延迟约束”,虽然它在某些数据库系统(如 PostgreSQL 和 Oracle)中受支持,但它不在 MySQL 中(据我所知)。它的基本意思是,不会在每个INSERTorUPDATE语句中检查诸如外键约束之类的关系,而只会在提交整个事务时检查,因此只要您之前清理过混乱,您就可以在中间阶段随意违反它们你完成了。

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

于 2013-05-18T01:11:43.663 回答
2

Jeremy Todd provided a good answer (+1 to him), I'd just like to make an additional point...

Is the "current" the same thing as "latest"? If yes, then you can use an identifying relationship and the resulting composite key to naturally model that:

enter image description here

All the revisions of the same page have the same PageRevision.PageId, and their historical order within the page is determined by the integer RevNo. The latest revision is simply the one with the highest RevNo within its respective page.

Since InnoDB tables are clustered, this structure will group the revisions of the same page physically close together. Retrieving all the revisions of the page will be potentially much quicker than in your original structure, and retrieving only the latest revision will be about as quick.

Data modification will also be quicker, since we have one less index.


I could drop Page.CurrentRevisions and add PageRevisions.isCurrent, but I don't like that this design would allow more than one revision for a Page to be marked as current

Not that I'd recommend this approach, but the uniqueness of the "current" flag can be enforced declaratively, simply by using NULL instead of 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);

NOTE: MySQL parses but doesn't enforce the CHECK constraint above. As a consequence you could have one (unwanted) false flag per page in addition to one (useful) true flag per page.

NOTE 2: Because of the peculiar nature of NULL, the CHECK above could be rewritten simply as: CHECK (IsCurrent = 1). When the flag is 0, the expression is false and the CHECK fails as expected. If the flag is 1 the expression is true and the CHECK passes. If the flag is NULL, the expression is NULL, and the CHECK passes (unlike WHERE which treats NULL as false). But I prefer to be a bit more explicit than that when dealing with NULLs.

于 2013-05-18T11:04:13.097 回答