在上學的時候,數據庫是一門讓我比較頭大的課程。記得當時教材上凈是一些晦澀難懂的語言,沒有充足的實例來幫助理解。前一陣子在看《網絡游戲服務器端編程》的過程中,突然對數據庫范式有了一些感覺,在此總結一下,分享給大家。作者純菜鳥,即使總結這些基礎知識也難免有錯,希望給位大牛不吝賜教,謝謝!
鍵(關系鍵)以及數據庫范式都是關系數據庫的概念。所謂關系鍵,指的是一個表中的一個(或一組)屬性,用來標識該表的每一行或與另一個表產生聯系。
數據庫的”范式“,指的是設計數據庫的規則。按照一定的規則設計出數據庫的表和關系,能夠避免在一些情況下的查詢出錯,並具有良好的結構。總的來說,隨着范式等級的提高,數據表屬性之間的依賴關系越來越小,數據冗余越來越低。但同時,數據關系變得更加復雜,訪問一個具體數據的關系層次增加。所以像設計模式一樣,不應盲目追求范式等級,應根據具體需求來選擇范式。
我們先來看一下幾種常見的數據庫關系鍵:
1、超鍵(super key):能夠唯一標識一條記錄的屬性或屬性集。
-
- 標識性:一個數據表的所有記錄都具有不同的超鍵
- 非空性:不能為空
2、候選鍵(candidate key):能夠唯一標識一條記錄的最小屬性集
-
- 標識性:一個數據表的所有記錄都具有不同的候選鍵
- 最小性:候選鍵的任何子集都不能唯一標識一個記錄
- 非空性:不能為空
- 候選鍵是沒有多余屬性的超鍵
3、主鍵(主碼、primary key):某個能夠唯一標識一條記錄的最小屬性集
-
- 唯一性:一個數據表只能有一個主鍵
- 標識性:一個數據表的所有記錄都具有不同的主鍵取值
- 非空性:不能為空
- 選取某個候選鍵為主鍵
4、外鍵(foreign key):子數據表中出現的父數據表的主鍵,稱為子數據表的外鍵。
5、代理鍵:當不適合用任何一個候選鍵作為主鍵時(如數據太長等),添加一個沒有實際意義的鍵作為主鍵,這個鍵就是代理鍵。(如常用的序號1、2、3)
6、自然鍵:自然生活中唯一能夠標識一條記錄的鍵(如身份證)
下面就來看一下常見的幾種關系數據庫范式吧。
一、第一范式(1NF)
要求:
- 每一個屬性都不能再分割,都是原子項。
第一范式是關系型數據表的基本要求,但是如何判斷一個屬性能否再分割呢?這沒有統一的標准,需要依照需求確定。比如,我們要設計一個網絡游戲后台所用的數據庫,其中有一個數據表,記錄有關於擊殺怪物所獲得的金錢:
| 編號 |
怪物名 |
掉落金錢 |
| 1 |
巨熊 |
100 |
這個表格看上去並沒有什么問題。每一個屬性項都是”不可分割“的,所以符合第一范式。但是,如果我們希望把玩家擊殺怪物之后獲得的金錢分成兩部分,一部分是固定收益,另一部分是一個隨機的浮動收益(比如和玩家幸運值有關)。則這張表格中的”掉落金錢“項就不是”不可分割“了,也就不符合第一范式了。如果有這種需求,我們就可以把”掉落金錢“分割為”固定金錢“和”浮動金錢“兩部分。如下所示:
| 編號 |
怪物名 |
固定金錢 |
浮動金錢 |
| 1 |
巨熊 |
80 |
20 |
這樣分割之后,使得每一項都不能再分割,從而使得數據表滿足第一范式。
滿足第一范式的數據表有什么好處呢?
- 1NF保證了數據庫的每一列都是不同的。每一列的數據彼此沒有任何交集。
- 這樣做首先減少了數據的冗余,節省存儲空間。如果不滿足第一范式,一些數據項有可能包含相同的”子項“,造成存儲空間的浪費。
- 其次,每一列沒有重復的數據意味着不需要考慮數據更新的同步問題。不用擔心在一列中更新了數據,還要在另一列做相應修改。
- 另外,每一列的數據不可再分,在某些情況下減少了數據訪問的層數,提高數據訪問速度。
二、第二范式(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 |
表中的依賴關系有:
- (玩家ID,房間ID)-> 房主ID
- 房主ID -> 房間ID
- (玩家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 |
單獨看這三個子表,我們可以得出以下結論:
- Alice和Bob對戰過
- Alice在競技場1和競技場2都進行過對戰
- Bob在競技場1和競技場2都進行過對戰(結合第二、三個表)
從這三個獨立的結論,我們無法得知Alice和Bob究竟在那個競技場進行的對戰,也就是發生了信息丟失。所以上邊那個表是符合5NF的。
好了,6個范式都看完啦,簡單總結一下:
| 范式等級 |
說明 |
| 1NF |
每一列都是原子項,不可分割 |
| 2NF |
非主鍵屬性均完全依賴於主屬性,消除部分依賴 |
| 3NF |
所有非主鍵屬性之間沒有依賴關系,消除傳遞依賴 |
| BCNF |
所有屬性均不傳遞依賴於任何候選鍵 |
| 4NF |
表中不包含超過一個多值屬性,消除多值依賴 |
| 5NF |
將表拆分為二元關系,一定會損失信息 |
感謝您看到這里!希望對您有一點幫助,歡迎批評和討論! ^_^
其他博客:
