[原創] 基礎中的基礎(三):理解數據庫的幾種鍵和幾個范式


  在上學的時候,數據庫是一門讓我比較頭大的課程。記得當時教材上凈是一些晦澀難懂的語言,沒有充足的實例來幫助理解。前一陣子在看《網絡游戲服務器端編程》的過程中,突然對數據庫范式有了一些感覺,在此總結一下,分享給大家。作者純菜鳥,即使總結這些基礎知識也難免有錯,希望給位大牛不吝賜教,謝謝!

  鍵(關系鍵)以及數據庫范式都是關系數據庫的概念。所謂關系鍵,指的是一個表中的一個(或一組)屬性,用來標識該表的每一行與另一個表產生聯系

  數據庫的”范式“,指的是設計數據庫的規則。按照一定的規則設計出數據庫的表和關系,能夠避免在一些情況下的查詢出錯,並具有良好的結構。總的來說,隨着范式等級的提高,數據表屬性之間的依賴關系越來越小,數據冗余越來越低。但同時,數據關系變得更加復雜,訪問一個具體數據的關系層次增加。所以像設計模式一樣,不應盲目追求范式等級,應根據具體需求來選擇范式。

 

  我們先來看一下幾種常見的數據庫關系鍵:

  1、超鍵(super key):能夠唯一標識一條記錄的屬性或屬性集。

    • 標識性:一個數據表的所有記錄都具有不同的超鍵
    • 非空性:不能為空

  2、候選鍵(candidate key:能夠唯一標識一條記錄的最小屬性集

    • 標識性:一個數據表的所有記錄都具有不同的候選鍵
    • 最小性:候選鍵的任何子集都不能唯一標識一個記錄
    • 非空性:不能為空
    • 候選鍵是沒有多余屬性的超鍵

  3、主鍵(主碼、primary key)某個能夠唯一標識一條記錄的最小屬性集

    • 唯一性:一個數據表只能有一個主鍵
    • 標識性:一個數據表的所有記錄都具有不同的主鍵取值
    • 非空性:不能為空
    • 選取某個候選鍵為主鍵

  4、外鍵(foreign key):子數據表中出現的父數據表的主鍵,稱為子數據表的外鍵。

  5、代理鍵:當不適合用任何一個候選鍵作為主鍵時(如數據太長等),添加一個沒有實際意義的鍵作為主鍵,這個鍵就是代理鍵。(如常用的序號1、2、3)

  6、自然鍵:自然生活中唯一能夠標識一條記錄的鍵(如身份證)

 

  下面就來看一下常見的幾種關系數據庫范式吧。

 

一、第一范式(1NF)

  要求:

  •   每一個屬性都不能再分割,都是原子項。

  第一范式是關系型數據表的基本要求,但是如何判斷一個屬性能否再分割呢?這沒有統一的標准,需要依照需求確定。比如,我們要設計一個網絡游戲后台所用的數據庫,其中有一個數據表,記錄有關於擊殺怪物所獲得的金錢:

編號

怪物名

掉落金錢

1

巨熊

100

   這個表格看上去並沒有什么問題。每一個屬性項都是”不可分割“的,所以符合第一范式。但是,如果我們希望把玩家擊殺怪物之后獲得的金錢分成兩部分,一部分是固定收益,另一部分是一個隨機的浮動收益(比如和玩家幸運值有關)。則這張表格中的”掉落金錢“項就不是”不可分割“了,也就不符合第一范式了。如果有這種需求,我們就可以把”掉落金錢“分割為”固定金錢“和”浮動金錢“兩部分。如下所示:

編號

怪物名

固定金錢

浮動金錢

1

巨熊

80

20

  這樣分割之后,使得每一項都不能再分割,從而使得數據表滿足第一范式。 

  滿足第一范式的數據表有什么好處呢?

  1. 1NF保證了數據庫的每一列都是不同的。每一列的數據彼此沒有任何交集。
  2. 這樣做首先減少了數據的冗余,節省存儲空間。如果不滿足第一范式,一些數據項有可能包含相同的”子項“,造成存儲空間的浪費。
  3. 其次,每一列沒有重復的數據意味着不需要考慮數據更新的同步問題。不用擔心在一列中更新了數據,還要在另一列做相應修改。
  4. 另外,每一列的數據不可再分,在某些情況下少了數據訪問的層數,提高數據訪問速度。

 

二、第二范式(2NF)

  要求:

  • 滿足第一范式
  • 非主鍵屬性均完全依賴於主鍵

   非主鍵屬性和主鍵可以有什么關系?1、完全依賴。2、部分依賴。3、不依賴(沒關系)。顯然第三種情況下,這個屬性就不應該放在這張數據表中。所以2NF要求非主鍵屬性完全依賴於主鍵,就是在消除非主鍵屬性對主鍵的部分函數依賴。既然是部分函數依賴,暗含着說主鍵是一個復合鍵(由多個屬性組成的鍵)。如果某個非主鍵屬性只和主鍵中的一部分有關(部分函數依賴),則不符合第二范式。舉例,網絡游戲的用戶數據表:

玩家用戶名

角色名

角色職業

上次登錄時間

 Alice

superman

wizard

2013-11-4 

  如果我們的游戲允許一個玩家擁有多個角色,則在這張表中“玩家用戶名”和“角色名”構成復合主鍵,唯一標識一條記錄。表中的“角色職業”,與玩家用戶名和角色名均相關,為完全依賴於主鍵。而“上次登錄時間”僅和“玩家用戶名”相關,而與角色名無關。所以“上次登錄時間”部分函數依賴於主鍵。本關系不符合2NF。

  要將上表轉換為符合2NF的結構也很簡單,只要把部分函數依賴的部分抽出來,組成新的表即可。如下所示:

玩家用戶名

角色名

角色職業

Alice

superman

wizard

 

玩家用戶名

上次登錄時間

Alice

2013-11-4

  符合2NF能給我們帶來什么好處呢?2NF消除了屬性對主鍵的部分函數依賴。

  首先,2NF可以在一定程度上消除冗余,節省存儲空間。

  如果存在部分函數依賴,則可能存在數據冗余。在多條記錄中,主鍵中的某一個屬性可能是一樣的,而如果有其他數據項函數依賴於這個不變的屬性,則這些數據項也將是一樣的。比如在上面例子中,在修改之前的表中,如果有多個角色名對應一個玩家用戶名,則會有多條數據。它們具有一樣的用戶名和不同的角色名。由於上次登錄時間僅依賴於玩家用戶名,所以在這多條記錄中,上次登錄時間也都是相同的,造成了冗余。

  其次,2NF簡化了表的邏輯關系,使得表的結構更加清晰。

 

三、第三范式(3NF)

  要求:

  • 滿足第一、二范式
  • 所有非主鍵屬性之間沒有函數依賴關系

  3NF在2NF的基礎上,進一步消除非主鍵屬性之間的函數依賴關系。實質上,也是消除非主鍵屬性中的傳遞依賴。更進一步地說,如果兩個數據表有關系。那么這兩個數據表中的非主鍵屬性必須是不同的。如果存在一個非主鍵屬性A,存在於兩張表中。則在某張表中,A依賴於外鍵,從而不符合3NF。比如網絡游戲中拍賣行的數據,可以按照下面的表格進行存儲:

玩家姓名

物品名

單價

數量

總金額

Alice

治療葯劑

50

10

500

  在這個表格中,“總金額”項可以通過“單價”和“數量”運算得出,存在函數依賴關系,不滿足3NF。

  要將這個表格修改為滿足3NF的要求,只需要從表中刪除“總金額”即可。在另外一些情況中,可以將函數依賴關系涉及到的項單獨抽出來組成新的表,需要具體情況具體分析。

  3NF的優點很明顯,可以減少數據冗余,節省存儲空間。既然存在函數依賴,某些數據項就能夠通過其他數據項計算得出,很可能存在數據冗余。值得注意的是,在一些情況下,存在這種數據冗余的表格是有意義的。如果在表格中存儲着某些運算的結果,我們在使用這些結果時就不用進行運算了,節省了運算時間,是一種“空間換時間”的做法。從這里也可以看出,應用范式並不能夠保證最好的效果,需要根據應用需求進行合理取舍。

  

四、BC范式(boyce-codd范式,BCNF)

  要求:

  • 滿足1NF、2NF、3NF 
  • 所有屬性(包含主鍵屬性和非鍵主屬性)都不傳遞依賴於任何候選鍵

  BC范式在3NF的基礎上,要求主鍵屬性也不能傳遞依賴於任何候選鍵。當主鍵是復合鍵是,主鍵的某個屬性可能會依賴於某個候選鍵。此時,關系能夠符合3NF,因為並不是“非主鍵”屬性依賴於某個非主鍵屬性。但此關系並不符合BC范式。例如,在以房間為組織方式的游戲中,我們記錄某個玩家、房間和房主的關系。

房主ID

房間ID

玩家ID

Alice

123

Bob

  表中的依賴關系有:

  1. (玩家ID,房間ID)-> 房主ID
  2. 房主ID -> 房間ID
  3. (玩家ID,房主ID)-> 房間ID

  同時,表中的候選鍵有(玩家ID,房間ID)、(玩家ID,房主ID)。比如,我們選擇主鍵為(玩家ID,房間ID),那么,房間ID就是主鍵的一個屬性。而在依賴關系2中,房間ID依賴於房主ID,房主ID是候選鍵(玩家ID,房主ID)的一個屬性。那么,首先,由於房間ID不是候選鍵屬性,所以此表並沒有違反3NF。但是由於房間ID和房主ID存在依賴關系,所以滿足“主鍵屬性傳遞依賴於某個候選鍵”的條件,所以此表不符合BC范式。

  要把上表修改為滿足BC范式的形式,只要把它進行合理拆分即可。

房間ID

玩家ID

123

Bob

 

房間ID

房主ID

123

Alice

   BC范式的好處是進一步消除了表中的依賴關系,減少了冗余。例如在上例中,如果我們采用未修改的版本,如果想要存儲一個10個玩家(不含房主)的房間,就需要10條這樣的記錄才可以。

  

五、第四范式

  要求:

  • 滿足1NF、2NF、3NF
  • 表中不能包含一個實體的兩個或多個多值屬性

  所謂多值屬性,指的是某個屬性可以包含多個值。這個屬性的(多個)取值,被另一個屬性決定。也就是說,一旦確定了某個屬性,另一個屬性的多個取值就一起確定了。第四范式在第三范式的基礎上,消除多值依賴。所謂多值依賴,指的是一組值(多值屬性)依賴於另一個屬性。函數依賴是一對一的關系,多值依賴是一對多的關系。這個理解起來我感覺有點別扭,可能我的理解也有偏差,說出來和大家一起探討一下。

  比如,我們要在數據庫中保存玩家的角色技能信息,這里我們允許一個玩家具有多個角色,一個角色具有多個技能:

 

玩家ID

角色名

技能

Alice

superman

Fire ball

  首先,這個表只有一個候選鍵(玩家ID、角色名、技能)。所以肯定符合3NF。進一步觀察一下,玩家ID是一個單值屬性。角色名就是一個多值屬性了,因為一個玩家ID可以對應多個角色名。角色名在表中看起來是一項,這是由於受制於具體數據庫提供的功能。邏輯上,我們拿到一個玩家ID,可以確定的是,這個玩家具有某些角色,是一個一對多的關系,是多值依賴。角色名是一個多值屬性。同樣的,一個角色也對應着多個技能,這也是多值依賴。技能也是一個多值屬性。顯然,這個表並不符合4NF。

  這個表有什么問題呢?

  首先,數據冗余大,如果一個玩家有好幾個具有Fire Ball技能的角色,這個技能項就要重復保存幾次。

  其次,增、刪、改都比較復雜,比如我們要刪除Fire Ball技能,那么,我們要刪除這個玩家所有具有Fire Ball技能的表項。

  要將上表修改為符合4NF的表,只需要將多值依賴進行合理映射即可:

玩家ID

角色名

Alice

superman

 

角色名

技能

superman

Fire ball

  這兩個表都符合4NF。

  可以看出,4NF的使用可以降低數據冗余,並且減少數據處理復雜度

  

六、第五范式

要求:

  • 滿足1NF、2NF、3NF、4NF
  • 如果將表中的多元關系分解一個一個的二元關系,一定會丟失信息

  第五范式在4NF的基礎上,進一步消除依賴。第五范式的要求明,如果不用這個表就不能正確說明數據之間的聯系。所以符合5NF的表已經沒有任何多余依賴的存在了。所以第五范式是一個比較理想的范式。比如我們存儲玩家對戰和其發生地點:

玩家1

玩家2

對戰地點

Alice

Lisa

競技場1

Alice

Bob

競技場2

Bob

Lisa

競技場1

  現在,我們把它拆分成三個二元關系:

玩家1

玩家2

Alice

Lisa

Alice

Bob

Bob

Lisa

 

玩家1

對戰地點

Alice

競技場1

Alice

競技場2

Bob

競技場1

 

玩家2

對戰地點

Lisa

競技場1

Bob

競技場2

Lisa

競技場1

 

  單獨看這三個子表,我們可以得出以下結論:

  1. Alice和Bob對戰過
  2. Alice在競技場1和競技場2都進行過對戰
  3. Bob在競技場1和競技場2都進行過對戰(結合第二、三個表)

 

  從這三個獨立的結論,我們無法得知Alice和Bob究竟在那個競技場進行的對戰,也就是發生了信息丟失。所以上邊那個表是符合5NF的。

 

  好了,6個范式都看完啦,簡單總結一下:

范式等級

說明

1NF

每一列都是原子項,不可分割

2NF

非主鍵屬性均完全依賴於主屬性,消除部分依賴

3NF

所有非主鍵屬性之間沒有依賴關系,消除傳遞依賴

BCNF

所有屬性均不傳遞依賴於任何候選鍵

4NF

表中不包含超過一個多值屬性,消除多值依賴

5NF

將表拆分為二元關系,一定會損失信息

 

 

  感謝您看到這里!希望對您有一點幫助,歡迎批評和討論! ^_^

 

  其他博客:

  基礎中的基礎(二):C/C++ 中 const 修飾符用法總結

  基礎中的基礎(一):簡單排序算法總結(附代碼)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM