2013-05-18 48 views
1

下面是两个表的一个非常简单的例子我必须存储的网页:存储修订时避免循环依赖性?

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

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

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

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

问题是这是一个循环关系。在MySQL中,强制执行外键时,如果不先创建PageRevision,则无法创建Page,如果不先创建页面,则无法创建PageRevision。

我可以放弃Page.CurrentRevisions并添加PageRevisions.isCurrent,但我不喜欢这种设计会允许一个页面的多个版本被标记为最新的 - 我宁愿数据库设计强制执行约束(不带触发器)。

回答

3

您要找的内容称为“延迟约束”,虽然它在某些数据库系统(如PostgreSQL和Oracle)中受支持,但它不在MySQL中(据我所知)。它的基本含义是,不会在每个INSERTUPDATE声明中检查诸如外键约束之类的关系,但只有在整个事务被提交时,才能在中间阶段自由违反它们,只要清理在你完成之前一团糟。

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

+0

非常感谢。你认为这个问题和MySQL缺乏对DEFERRED CONSTRAINT的支持是否表明我选择了糟糕的设计?我想知道是否应该创建第三张表来定义Page和PageRevision之间的关系。 – user2045006

+0

不,我认为在您的CurrentRevisions列中允许空值并依赖业务逻辑来保持事物一致性在这种情况下是完全没问题的。中小型应用程序最近的趋势,特别是像ORM框架这样的事情,无论如何都要依赖业务规则来强制执行数据完整性。如果可能的话,确保将所有内容都包含在交易中。 –

+0

我还应该补充说,即使允许使用空值,仍然可以使用外键约束,因此验证和级联删除等操作仍然可行。唯一的危险是你在'Page'表中有一个空的当前版本的记录的时间很短,但如果你使用一个事务,这是一个非问题。 –

2

杰里米·托德提供good answer(+1给他),我只想做一个额外的点...

是“当前”同样的事情,“最新”?如果是的话,那么你可以使用一个标识的关系,得到的复合键自然模型:

enter image description here

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

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

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


我可能会下降Page.CurrentRevisions并添加PageRevisions。isCurrent,但我不喜欢这种设计将允许一个页面的多个版本被标记为当前

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

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,则该表达式为真并且CHECK通过。如果标志为NULL,则表达式为NULL,并且检查通过(与将NULL视为false的WHERE不同)。但是我更喜欢在处理NULL时比这更明确。

+0

感谢您的替代建议。那里有好东西,我没有完全意识到。 – user2045006