主鍵什麼時候應該有意義?
請原諒這裡的任何定義的鬆散,但我試圖探索一個簡單的概念。
主鍵唯一標識一行。表中可能還有其他列具有唯一值,因此它們也可以唯一標識一行(候選鍵),但主鍵是為任務指定的。
使主鍵更有用的屬性包括:
- 保證唯一;隨著表的增長,其他一些唯一的列值可能會重複
- 不太可能改變;雖然外鍵約束可以包括級聯,但最好不要這樣
- 不會被回收;由於某種原因被刪除的行不應釋放舊的 PK 值以重用
由於這些原因,我通常建議主鍵沒有內在價值,因此永遠沒有理由更改或回收價值。也就是說,它應該是沒有意義的。
我見過包含某種程式碼的主鍵,例如基於名稱的客戶端程式碼。明顯的問題是(a)如果客戶端名稱更改,那麼 PK 也應該更改,以及(b)與具有相似名稱的客戶端發生衝突的風險太大。
一個例外是使用自動遞增的數字,它具有序列號的次要含義。但是,它仍然是穩定的。
問題是,在什麼情況下(如果有的話)最好使用具有其他實際含義的主鍵?也就是說,PK應該是任意的,你通常可以通過序列號的建議有什麼問題嗎?
在什麼情況下(如果有的話)最好使用具有其他實際含義的主鍵?(重點補充)
鑑於這個問題的重點是“可取”而不是“可接受”,並且接受這仍然是一個高度主觀的話題,我會說我想不出一個最好的情況,即係統最好有一個真正自然的出於各種原因的關鍵(其中大部分已經在之前保羅在對該問題的評論中連結到的其他答案中說過):
- 被認為是唯一的並不總是唯一的(例如美國的社會安全號碼/SSN)
- 有時事情會發生變化,無論是價值還是獨特性(我們無法控制外部世界)
- 即使某些東西在價值和唯一性上應該是“穩定的”(例如 SKU,也許),是否可以保證傳入的值是正確的?人類在輸入數據時經常會打錯東西。導出過程中也存在可能導致系統導入的文件中的值不正確的錯誤。其他系統中也存在將數據輸入您的系統的錯誤,這些錯誤可能導致數據本身不完全正確,即使它們的導出過程正常工作。
我強調“真實”,因為在兩種情況下我更喜歡沒有新的代理鍵:
橋牌桌
(或者任何你喜歡呼叫的表,僅用於或主要用於表示多對多關係)
Thing ThingXTag Tag ------ --------- --- ThingID INT AutoMagic PK ---> ThingID INT PK, FK Stuff SomeType TagID INT PK, FK <--- TagID INT AutoMagic PK TagName VARCHAR
在對橋接表(邏輯模型中不存在但物理模型中需要的表)建模時,PK 應該存在通過該表關聯的表的現有主鍵列。這允許強制執行值的正確唯一性和非空性,而無需單獨的唯一索引/約束。在這種關係需要外鍵的極少數情況下,它將:
- 有意義的是,每個鍵列實際上都將指向原始源表,而無需再次連接,並且
- 防止某人或某物在不更新通過外鍵連結到的值的情況下更新形成兩個主表之間關係的鍵列。
WackyTable ThingXTag ---------- --------- WackyTableID INT AutoMagic PK ThingID INT FK ---> ThingID INT PK, FK (to Thing.ThingID) TagID INT FK ---> TagID INT PK, FK (to Tag.TagID) AttributeX VARCHAR InsertDate DATETIME InsertDate DATETIME
我在一個系統上工作過,這些橋表有自己的自動遞增代理鍵 PK,橋表的單列代理鍵通過 FK 在其他表中引用:
WackyTable ThingXTag ---------- --------- WackyTableID INT AutoMagic PK ThingXTagID INT FK ---> ThingXTagID INT AutoMagic PK AttributeX VARCHAR ThingID INT FK (to Thing.ThingID) InsertDate DATETIME TagID INT FK (to Tag.TagID) InsertDate DATETIME
這是一個可怕的、令人困惑的混亂,我們在調試等方面浪費了太多時間。
同級表
這些表是真正的單個實體,因此具有一對一的關係。出於性能原因,它們僅分為兩個(或更多,根據需要)表。我已經對具有 100 萬(或更多)行的表進行了此操作,這些行要麼非常寬,要麼中等寬*,並且*有些列要麼不經常使用,要麼是超過 50 個字節的字元串。類似的東西。這將實體的核心屬性保留在一個更窄的表中,該表適合每個數據頁上的更多行。
在這些情況下,“兄弟”表與初始表處於完全相同的級別,並且應該具有相同的
PK
. 給它一個自動遞增的代理鍵是沒有用的,因為每一行都有一個來自初始表的自然鍵。Product ProductProperty ------- --------------- ProductID INT AutoMagic PK ---> ProductID INT PK, FK (to Product.ProductID) Name VARCHAR ShortDescription VARCHAR SKU VARCHAR SomethingElse SomeType Quantity INT UpdateDate DATETIME CreateDate DATETIME UpdateDate DATETIME
**需要明確的是,我說的是物理模型,而不是概念模型。我假設這個問題的重點是物理模型,因為它是在概念上不存在的問題的背景下建構的:代理鍵、處理主鍵值使用的問題等。考慮到這一點,我不是這意味著不應儲存自然密鑰並將其用於辨識。相反,自然鍵是很好的“備用鍵”,應該在它們上面放置唯一的約束/索引。然而,概念模型的理想主義並不總是直接轉化為物理模型。數據完整性(即數據模型的穩定性和可靠性)是最重要的,如果不是top,物理模型的優先級。因此,必須進行實際考慮,例如使用代理鍵,以確保實現這一目標並且不受影響。這意味著,如果您有 SSN 或 SKU 等,那麼絕對將它們儲存在具有唯一約束的列中,並讓系統*對該值進行查找,因為無論如何都不應該*在外部使用自動生成的數字。使用者不需要知道記錄的自動生成的 ID 號:他們應該傳遞他們知道的值(例如,電子郵件地址作為 UserID / CustomerID 的查找,航班確認程式碼與航班日期相結合等)並且系統應該將其轉換為從那時起使用的自動生成的值。
是的,在使用自然鍵作為備用鍵時,此答案開頭提到的問題仍然是潛在問題。但是,不同之處在於問題(通常)僅與一張表隔離。如果有人犯了錯誤並在“航班定位器”上創建了唯一索引,那麼他們可能需要一段時間才能獲得違規。但是一旦他們這樣做了,就很容易刪除該唯一索引並重新創建它以包含航班日期。或者,如果您更改系統上的電子郵件地址(通常用作登錄名)並因為幾年前(合法)被其他人使用而出現錯誤,則很可能由支持人員處理,而不會產生任何影響/風險現有的相關記錄。在這兩種情況下,數據模型的其餘部分都沒有進行必要的更改。
同樣,這是一種務實的方法:
- 最小化遷移一個或多個外鍵引用的主鍵時可能發生的潛在數據失去(並且最小化項目範圍也減少了維護視窗:-)。雖然並非所有 PK(或唯一約束/索引)都具有 FK,但使用代理肯定會減少具有此類依賴關係的列數
- 使系統盡可能耐用、有彈性和高效。物理模型是概念模型的“在實踐中”到“理論上”。鑑於概念模型不關心實現,我們已經做了很多調整和考慮。然而,我們選擇利用供應商特定的功能,以提高性能(例如,非規範化、查找表、如上所述的“兄弟”表等),以創建“橋”表(如上所述)等等。
我不知道有多少系統使用 SSN(美國的社會安全號碼)作為 PK,但對於任何這樣做的系統,其中一些(也許很多)可能已經避免了它們沒有像應有的那樣獨特的問題。但是,這些系統中沒有一個能夠避免多年來關於需要更安全地處理它們的變化。將 SSN 視為備用密鑰的系統需要很少的開發時間來切換到加密這些值,並且系統需要很少的停機時間(或沒有停機時間)來在數據層進行更改。鑑於我們都有可能永遠無法完成的項目積壓,企業往往更喜歡這些煩人但不可避免的更改將花費他們 5 小時而不是 20 到 40 小時(不要忘記更改需要測試,
明確地說,在某些情況下使用自然鍵是“可以接受的”,儘管我認為我不會說“首選”。
- 州/地區和國家/地區程式碼:如果您使用由國際標準化組織 (ISO)維護的程式碼(例如“US”= 美國,“FL”= 佛羅里達等),那麼這些程式碼可能足夠可靠,可以使用,我有使用它們是因為它們很短(即對性能來說還不錯)並且易於閱讀。這些程式碼也經常以各種其他方式使用,即使在電腦系統之外,人們也普遍熟悉它們,即使是最初對某些人沒有那麼大意義的那些(例如“DE”=對於那些不知道德語中的“德國”==“德國”的人來說,德國可能並不直覺)。
- **內部查找值程式碼:**您無法控制外部來源,但(希望)您可以控制自己的系統。如果您的系統有內部使用的部門程式碼、狀態程式碼等,那麼為它們提供短程式碼(2 - 4 個字節)應該沒問題。在 4 個字節時,它將使用與 an 相同的空間量,
INT
如果使用二進制排序規則(以_BIN2
或什至 結尾_BIN
,但_BIN2
首選),那麼它應該比較快。對此類程式碼具有相對有意義的值可以使支持/調試更容易。但是,您仍然會遇到這樣的情況,隨著時間的推移,部門名稱等可能會發生變化,並且程式碼可能不再有意義。