邏輯數據庫設計 - 需要ID(談主鍵Id)

本文的目標就是要確認那些使用了主鍵,卻混淆了主鍵的本質而造成的一種反模式。

一、確立主鍵規範

  每個瞭解數據庫設計的人都知道,主鍵對於一張表來說是一個很重要,甚至必需的部分。這確實是事實,主鍵是好的數據庫設計的一部分。主鍵是數據庫確保數據行在整張表唯一性的保障。它是定位到一條記錄並且確保不會重複存儲的邏輯機制。主鍵也同時可以被外鍵引用來建立表與表之間的關係。

  難點是選擇那一列作爲主鍵。大多數表中的每個屬性值都有可能被很多行使用。例如姓名,電子郵件地址等等都不能保證不會重複。

  在這樣的表中,需要引入一個對於表的域模型無意義的新列來存儲一個僞值。這一列被用作這張表的主鍵,從而通過它來確定表中的一條記錄,即便其他的列允許出現適當的重複項。這種類型的主鍵列我們通常稱其爲僞主鍵或者代理鍵

  大多數數據庫提供一種和當前處理事務無關的底層方案,來確保每次都能生成全局唯一的一個整數作爲僞主鍵,即使客戶端此時正發起併發操作。

  主鍵存在的作用:

  1、確保一張表中的數據不會出現重複行。

  2、在查詢中引用單獨的一行記錄。

  3、支持外鍵。

二、反模式:以不變應萬變

  很多的書、文章以及程序框架都會告訴你,每個數據庫的表都需要一個主鍵,且具有如下三個特性:

  (1)、主鍵的列名都叫做Id;

  (2)、數據類型是32位或者64位的整數

  (3)、主鍵的值是自動生成來確保唯一的。

  在每張表中都存在一個叫做Id的列是如此地平常,甚至Id已經成爲了主鍵的同義詞。很多程序員在一開始學習SQL時就被灌輸了錯誤的概念。認爲每張表都要增加一列Id,這顯然太過於隨意。

  1、冗餘鍵值

  2、允許重複項

  一個組合鍵包含了多個不同的列,組合鍵的典型場景就是想上節中亂穿馬路中的Contact表。主鍵需要確保一個給定的Product_Id和Account_Id的組合在整張表中只能出現一次,雖然同一個值可能在很多不同的配對出現。

  然而,當你使用了Id這一列作爲主鍵,約束就不是Account_Id和Product_Id的組合必須唯一了。當你用這張交叉表去查詢Account_Id和Product_Id的關係時,重複項會意料之外的結果。要確保沒有重複項,你可以在Id之外,額外聲明另外兩列需要一個UNIQUE約束。但是當你在Account_Id和Product_Id這兩列上應用了唯一性約束,Id這一列就會變成了多餘的列。它已經背離了主鍵的初衷了。

  

  3、關鍵字意義不明確

  Id這個詞是如此地普通,完全無法表達更深沉的意思,特別是在你做兩張表連接查詢,而他們都有一個叫做Id的主鍵時。

  select *
  from Accout as a
  join Bug b on (b.toId = a.Id)  

  這種查詢必須在查詢時指定列別名,否則其中的一個Id列會覆蓋掉另一列的Id的值。

  同時有兩張表有相同的Id列的情況下也不能夠使用using關鍵字。

  比如:SQL支持一種簡潔的表達式來表示兩張表聯結(using)。如果兩張表有同樣的列名,就可以用如下的表達式來重寫上面的需求。

select * from Account join Bug using(Bug_id);  --一個主鍵,一個外鍵。

  然而,如果所有的表都要求定義一個叫做id的僞主鍵,那麼將不能使用這種簡寫方式。 

  4、使用組合鍵

  一些開發人員覺得組合鍵太難使用,如果要比較兩個鍵值,必須比較其包含的所有列的值;一個引用組合鍵的外鍵,其本身也必須是一個組合外鍵。此外,使用組合鍵需要打更多的字。其實這是不對的。組合鍵的適當的時候是應該被使用的。

三、識別反模式

   1、我覺得這張表不需要主鍵。

  這麼說的人一定是誤解了“主鍵”和“僞主鍵”的含義,每張表都必須要有一個主鍵,這個是毫無疑問的。實際上可能這個人需要的是一個組合鍵,或者一個更自然的列名來做主鍵。

  2、我怎麼能在多對多的表中存儲重複的項?

  在一個對多對關係的交叉表中需要聲明一個主鍵約束。或者至少需要有一個針對那些被引用作爲外鍵的唯一約束。

四、合理使用發模式

   使用僞主鍵,或者通過自動增長的整型的機制本身沒有什麼錯誤,但不是每張表都需要一個僞主鍵,更沒有必要將每個僞主鍵都定義成Id。

   對於太長而不方便實現的自然鍵來說,僞主鍵是一個很好的代替品。比如在一個記錄文件系統的所有文件屬性的表中,文件路徑是一個很好的自然鍵,但對一個字符串列做索引的開銷會很大。

五、解決方案:裁剪設計

   主鍵是約束而非數據類型。你可以定義任意列或任意多個的列作爲主鍵。只要其數據類型支持索引。另外在此必須要補充的是,在SqlServer中,主鍵和聚集索引並沒必然的關係。SqlServer只是默認將聚集索引建在主鍵上,實際上你完全可以將聚集索引手動定義到非主鍵列。

   1、爲主鍵選擇更有意義的名稱

  比如爲Product這張表的主鍵應該叫做Product_Id。

  2、外鍵應該儘可能地和所引用的列使用相同的名稱,這通常意味着:一個主鍵的名稱應該在整個數據庫的設計中唯一;任意兩張表都不應該使用相同的名稱來定義主鍵,除非其中之一引用了另外一個作爲外鍵。然而凡是都有例外,又是外鍵的名稱需要和其所引用的主鍵區分開,從而使的它們之間的引用關係表現得更加清晰。

   比如在一張外鍵表中將外鍵聲明爲create_by(由誰創建)。  

 

規則 自然鍵 代理鍵
主鍵必須唯一的識別每一記錄 但與輸入和人爲錯誤有關 系統自生成的數據是唯一的
一個記錄的主鍵不能爲空 只有數據可知時才能輸入記錄 當記錄生成時才被系統建立
當生成記錄時,主鍵的值必須存在 只有數據可知時才能輸入記錄 當記錄生成時才被系統建立當記錄生成時才被系統建立
主鍵必須保持穩定——你不能更改主鍵的域 自然鍵與一些商業規則和其他外部影響有關 代理鍵對程序功能和數據保持中立
主鍵必須簡潔,不要包含過分的屬性 一個自然鍵可以包含多個域 代理鍵只能包含多個域
主鍵的值不能改變 自然鍵通常改變 代理鍵通常不更改

六、擁抱自然鍵和組合鍵

  如果你的表中包含一列能夠確保唯一、非空以及能夠用來定位一條記錄,就別僅僅因爲傳統而覺得有必要再加上一個僞主鍵。

  實踐證明,一張表中的每一列都在最初的設計之後遭遇改變是很正常的事情。數據庫的設計趨向於在整個項目的聲明週期中不斷地調整和優化,並且決策者也可能一點也不在乎自然鍵的“神聖不可侵犯”。有時候一個列最開始是像是個很好的自然主鍵,但隨後有允許合法的重複項。此時僞主鍵便成了唯一的選擇。

  在合適的時候也可以使用聯合主鍵,比如一條記錄可以通過多個列的組合完全定位。就像上面提到的Contact表,那就通過那些列創建一個聯合主鍵吧。

  總結:我個人的見解就是能用代理鍵就儘量用代理鍵。除非代理鍵真的非常多餘,就好似上面的組合鍵代替複合鍵的例子一樣。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章