范式與反范式
在學習數據庫設計的過程中,我們經常會遇到“范式”這個詞,什么是范式呢?
有的時候,我們在設計數據庫時,不僅需要知道怎么滿足范式,還需要考慮是否要進行反范式的設計,為什么呢?
什么是范式?
通俗理解,范式就是一種設計關系數據庫的規范。
范式來自英文Normal form,簡稱NF。要想設計—個好的關系,必須使關系滿足一定的約束條件,此約束已經形成了規范,分成幾個等級,一級比一級要求得嚴格。滿足這些規范的數據庫是簡潔的、結構明晰的,同時,不會發生插入(insert)、刪除(delete)和更新(update)操作異常。反之則是亂七八糟,不僅給數據庫的編程人員制造麻煩,而且面目可憎,可能存儲了大量不需要的冗余信息。
關系數據庫有六種范式:第一范式(1NF)、第二范式(2NF)、第三范式(3NF)、巴斯-科德范式(BCNF)、第四范式(4NF)和第五范式(5NF,又稱完美范式)。滿足最低要求的范式是第一范式(1NF)。在第一范式的基礎上進一步滿足更多規范要求的稱為第二范式(2NF),其余范式以次類推。一般來說,數據庫只需滿足第三范式(3NF)就行了。
下面,對各種范式進行詳細的介紹。
基礎概念
要想知道什么是范式,需要先對數據庫設計中一些基本的名詞進行簡單的了解:
-
單一的數據結構----關系(表文件)。關系數據庫的表采用二維表格來存儲數據,是一種按行與列排列的具有相關信息的邏輯組,它類似於Excel工作表。一個數據庫可以包含任意多個數據表。
在用戶看來,一個關系模型的邏輯結構是一張二維表,由行和列組成。這個二維表就叫關系,通俗地說,一個關系對應一張表。 -
元組(記錄)。表中的一行即為一個元組,或稱為一條記錄。
-
屬性(字段)。數據表中的每一列稱為一個字段,表是由其包含的各種字段定義的,每個字段描述了它所含有的數據的意義,數據表的設計實際上就是對字段的設計。創建數據表時,為每個字段分配一個數據類型,定義它們的數據長度和其他屬性。字段可以包含各種字符、數字、甚至圖形。如錯誤!未找到引用源。
-
屬性值。行和列的交叉位置表示某個屬性值,如“數據庫原理”就是課程名稱的屬性值
-
主屬性。一個屬性只要在任何一個候選碼中出現過,這個屬性就是主屬性。
-
非主屬性。與上面相反,沒有在任何候選碼中出現過,這個屬性就是非主屬性。
-
主碼。主碼(也稱主鍵或主關鍵字),是表中用於唯一確定一個元組的數據。關鍵字用來確保表中記錄的唯一性,可以是一個字段或多個字段,常用作一個表的索引字段。每條記錄的關鍵字都是不同的,因而可以唯一地標識一個記錄,關鍵字也稱為主關鍵字,或簡稱主鍵。如錯誤!未找到引用源。
-
全碼。如果一個碼包含了所有的屬性,這個碼就是全碼。
-
關系模式。關系的描述稱為關系模式。對關系的描述,一般表示為:關系名(屬性1,屬性2.....屬性n)。例如上面的關系可描述為:課程(課程號、課程名稱、學分、任課老師)。
但是關系模型的這種簡單的數據結構能夠表達豐富的語義,描述出現實世界的實體以及實體間的各種關系。
第一范式
第一范式(1NF):屬性不可分。
在前面已經介紹了屬性值的概念,我們說,它是“不可分的”。而第一范式要求屬性也不可分。那么它和屬性值不可分有什么區別呢?給一個例子:
這個表中,屬性值“分”了。“電話”這個屬性里對於“小明”屬性值分成了兩個。
這兩種情況都不滿足第一范式。不滿足第一范式的數據庫,不是關系數據庫!所以,我們在任何關系數據庫管理系統中,做不出這樣的“表”來。針對上述情況可以做成這樣的表:這個表中,屬性 “分”了。也就是“電話”分為了“手機”和“座機”兩個屬性。
第二范式
第二范式(2NF):符合1NF,並且,非主屬性完全依賴於碼。(注意是完全依賴不能是部分依賴,設有函數依賴W→A,若存在XW,有X→A成立,那么稱W→A是局部依賴,否則就稱W→A是完全函數依賴)
一個學生上一門課,一定是特定某個老師教。所以有(學生,課程)->老師;
一個學生上一門課,一定在特定某個教室。所以有(學生,課程)->教室;
一個學生上一門課,他老師的職稱可以確定。所以有(學生,課程)->老師職稱;
一個學生上一門課,一定是特定某個教材。所以有(學生,課程)->教材
一個學生上一門課,一定在特定時間。所以有(學生,課程)->上課時間
因此(學生,課程)是一個碼。
然而,一個課程,一定指定了某個教材,一年級語文肯定用的是《小學語文1》,那么就有課程->教材。(學生,課程)是個碼,課程卻決定了教材,這就叫做不完全依賴,或者說部分依賴。出現這樣的情況,就不滿足第二范式!
有什么不好嗎?你可以想想:
1、校長要新增加一門課程叫“微積分”,教材是《大學數學》,怎么辦?學生還沒選課,而學生又是主屬性,主屬性不能空,課程怎么記錄呢,教材記到哪呢? ……郁悶了吧?(插入異常)
2、下學期沒學生學一年級語文(上)了,學一年級語文(下)去了,那么表中將不存在一年級語文(上),也就沒了《小學語文1》。這時候,校長問:一年級語文(上)用的什么教材啊?……郁悶了吧?(刪除異常)
3、校長說:一年級語文(上)換教材,換成《大學語文》。有10000個學生選了這門課,改動好大啊!改累死了……郁悶了吧?(修改/更新異常,在這里你可能覺得直接把教材《小學語文1》替換成《大學語文》不就可以了,但是替換操作雖然計算機運行速度很快,但是畢竟也要替換10000次,造成了很大的時間開銷)
那應該怎么解決呢?投影分解,將一個表分解成兩個或若干個表。
第三范式
第三范式(3NF):符合2NF,並且,消除傳遞依賴(也就是每個非主屬性都不傳遞依賴於候選鍵,判斷傳遞函數依賴,指的是如果存在"A → B → C"的決定關系,則C傳遞函數依賴於A。)
上面的“學生上課新表”符合2NF,但是它有傳遞依賴!在哪呢?問題就出在“老師”和“老師職稱”這里。一個老師一定能確定一個老師職稱。(學生,課程)->老師->職稱。
有什么問題嗎?想想:
1、老師升級了,變教授了,要改數據庫,表中有N條,改了N次……(修改異常)
2、沒人選這個老師的課了,老師的職稱也沒了記錄……(刪除異常)
3、新來一個老師,還沒分配教什么課,他的職稱記到哪?……(插入異常)
那應該怎么解決呢?和上面一樣,投影分解:
巴斯范式
BC范式(BCNF):符合3NF,並且,主屬性不依賴於主屬性(也就是不存在任何字段對任一候選關鍵字段的傳遞函數依賴)
BC范式既檢查非主屬性,又檢查主屬性。當只檢查非主屬性時,就成了第三范式。滿足BC范式的關系都必然滿足第三范式。
還可以這么說:若一個關系達到了第三范式,並且它只有一個候選碼,或者它的每個候選碼都是單屬性,則該關系自然達到BC范式。
給你舉個例子:假設倉庫管理關系表 (倉庫ID, 存儲物品ID, 管理員ID, 數量),且有一個管理員只在一個倉庫工作;一個倉庫可以存儲多種物品。
這個數據庫表中存在如下決定關系:
(倉庫ID, 存儲物品ID) →(管理員ID, 數量)
(管理員ID, 存儲物品ID) → (倉庫ID, 數量)
所以,(倉庫ID, 存儲物品ID)和(管理員ID, 存儲物品ID)都是StorehouseManage的候選關鍵字,表中的唯一非關鍵字段為數量,它是符合第三范式的。但是,由於存在如下決定關系:
(倉庫ID) → (管理員ID)
(管理員ID) → (倉庫ID)
即存在關鍵字段決定關鍵字段的情況,所以其不符合BCNF范式。它會出現如下異常情況:
- 刪除異常:
當倉庫被清空后,所有"存儲物品ID"和"數量"信息被刪除的同時,"倉庫ID"和"管理員ID"信息也被刪除了。
- 插入異常:
當倉庫沒有存儲任何物品時,無法給倉庫分配管理員。
- 更新異常:
如果倉庫換了管理員,則表中所有行的管理員ID都要修改。
把倉庫管理關系表分解為二個關系表:
倉庫管理:StorehouseManage(倉庫ID, 管理員ID);
倉庫:Storehouse(倉庫ID, 存儲物品ID, 數量)。
這樣的數據庫表是符合BCNF范式的,消除了刪除異常、插入異常和更新異常。
一般,一個數據庫設計符合3NF或BCNF就可以了。在BC范式以上還有第四范式、第五范式。
第四范式
第四范式:要求把同一表內的多對多關系刪除。
第五范式
第五范式:從最終結構重新建立原始結構。
總結
其實數據庫設計范式這方面重點掌握的就是1NF、2NF、3NF、BCNF。
四種范式之間存在如下關系:
這里主要區別3NF和BCNF,一句話就是3NF是要滿足不存在非主屬性對候選碼的傳遞函數依賴,BCNF是要滿足不存在任一屬性(包含非主屬性和主屬性)對候選碼的傳遞函數依賴。
什么是反范式
數據庫中的數據反范式某種意義上來說就是一種以空間換時間的手段。
眾所周知,數據規范化優點是減少了數據冗余,節約了存儲空間,相應邏輯和物理的I/O次數減少,同時加快了增、刪、改的速度。但是對完全規范的數據庫查詢,通常需要更多的連接操作,從而影響查詢速度。因此,有時為了提高某些查詢或應用的性能而破壞規范規則,即反規范化(非規范化處理)。
范式與反范式的比較
- 查詢記錄時,范式模式往往要進行多表連接,而反范式只需在同一張表中查詢,當數據量很大的時候,顯然反范式的效率會更好。
- 反范式有很多重復的數據,會占用更多的內存,查詢時可能會較多地使用GROUP BY或DISTINCT等耗時耗性能的關鍵字。
- 當要修改更新數據時,范式更靈活,而反范式要修改全部的數據,且易出錯。
常見的反規范化技術包括
增加冗余列
增加冗余列是指在多個表中具有相同的列,它常用來在查詢時避免連接操作。例如:以規范化設計的理念,學生成績表中不需要字段“姓名”,因為“姓名”字段可以通過學號查詢到,但在反規范化設計中,會將“姓名”字段加入表中。這樣查詢一個學生的成績時,不需要與學生表進行連接操作,便可得到對應的“姓名”。
增加派生列
增加派生列指增加的列可以通過表中其他數據計算生成。它的作用是在查詢時減少計算量,從而加快查詢速度。例如:訂單表中,有商品號、商品單價、采購數量,我們需要訂單總價時,可以通過計算得到總價,所以規范化設計的理念是無須在訂單表中設計“訂單總價”字段。但反規范化則不這樣考慮,由於訂單總價在每次查詢都需要計算,這樣會占用系統大量資源,所以在此表中增加派生列“訂單總價”以提高查詢效率。
重新組表
重新組表指如果許多用戶需要查看兩個表連接出來的結果數據,則把這兩個表重新組成一個表來減少連接而提高性能。
分割表
有時對表做分割可以提高性能。表分割有兩種方式。
水平分割
根據一列或多列數據的值把數據行放到兩個獨立的表中。水平分割通常在下面的情況下使用。
情況 1:表很大,分割后可以降低在查詢時需要讀的數據和索引的頁數,同時也降低了索引的層數,提高查詢效率。
情況 2:表中的數據本來就有獨立性,例如表中分別記錄各個地區的數據或不同時期的數據,特別是有些數據常用,而另外一些數據不常用。
情況 3:需要把數據存放到多個介質上。
垂直分割
把主碼和一些列放到一個表,然后把主碼和另外的列放到另一個表中。如果一個表中某些列常用,而另外一些列不常用,則可以采用垂直分割,另外垂直分割可以使得數據行變小,一個數據頁就能存放更多的數據,在查詢時就會減少I/O次數。其缺點是需要管理冗余列,查詢所有數據需要連接操作。
總結
在設計表中,需要根據實際情況靈活選擇使用范式還是反范式設計表。
如果我們對查找的時效性要求比較高,而對空間占用要求比較低,可以采用反范式化設計。
范式參考:
https://www.cnblogs.com/lca1826/p/6601395.html
https://baike.baidu.com/item/數據庫范式/7309898
反范式參考:
https://zhuanlan.zhihu.com/p/364719109