2013-05-18 52 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