24. 資料庫設計規範
一、正規化
1.1、正規化概述
在關係型資料庫中,關於資料表設計的基本原則、規則就稱為正規化。可以理解為,一張資料表的設計結
構需要滿足的某種設計標準的級別 。要想設計一個結構合理的關係型資料庫,必須滿足一定的正規化。正規化的英文名稱是Nornal Form,簡稱NF。正規化是關係型資料庫理論的基礎,也是我們在設計資料庫結構中所要循序的規則和指導方法。
目前關係型資料庫有六種常見正規化,按照正規化級別,從低到高分別是:第一正規化(1NF)、第二正規化2NF)、第三正規化(3NF)、巴斯-科德正規化(BCNF)、第四正規化(4NF)和第五正規化(5NF,又稱完美正規化)。資料庫的正規化越高階,冗餘度就越低,同時高階的正規化一定符合低階正規化的要求,滿足最低要求的正規化是第一正規化(1NF)。在第一正規化的基礎上進一步滿足更多規範要求的稱為第二正規化(2NF),其餘正規化以此類推。一般來說,在關係型資料庫設計中,最高也就遵循到BCNF
正規化的優點:資料的標準化有助於消除資料庫中的資料冗餘,第三正規化(3NF)通常被認為在效能、擴充套件性和資料完整性方面達到了最好的平衡。
正規化的缺點:正規化的使用,可能會降低查詢的效率。因為正規化的等級越高,設計出來的資料表就越多、越精細,資料的冗餘度就越低,進行資料查詢的時候就可能需要關聯多張表,這不但代價昂貴,也可能是一些索引策略無效。
正規化只是提出了設計的標準,實際上設計資料表時,未必一定要符合這些標準。開發中,我們會出現為了效能和讀取效率違反正規化化的原則,通過增加少量的冗餘
正規化的定義會使用到主鍵和候選鍵,資料庫中的鍵(key)由一個或者多個屬性組成。資料表中常用的幾種鍵和屬性的定義:
- 超鍵:能唯一標識元組的屬性集叫做超鍵
- 候選鍵:如果超鍵不包括多餘的屬性,那麼這個超鍵就是候選鍵
- 主鍵:使用者可以從候選鍵中選擇一個作為主鍵
- 外來鍵:如果資料表R1中某個屬性集不是R1的主鍵,而是另一個數據表R2的主鍵,那麼這個屬性集就是資料表R1的外來鍵。
- 主屬性:包含在任一候選鍵中的屬性稱為主屬性
- 非主屬性:與主屬性相對,指的是不包含在任何一個候選鍵中的屬性
通常,我們將候選鍵稱之為碼,把主鍵也稱為主碼。因為鍵可能是由多個屬性組成的,針對單個屬性,我們還可以用主屬性和非主屬性來進行區分。
1.2、第一正規化
第一正規化主要是確保資料表中每個欄位的值必須具有原子性,也就是說資料表中每個欄位的值為不可再次拆分的最小資料單元。我們在設計某個欄位的時候,對於欄位X來說,不能把欄位X拆分成欄位X-1和欄位X-2。事實上,任何的DBMS都會滿足第一正規化的要求,不會將欄位進行拆分。
1.3、第二正規化
第二正規化要求,在滿足第一正規化的基礎上,還要滿足資料表裡的每一條資料記錄,都是可唯一標識的。而且所有非主鍵欄位,都必須完全依賴主鍵,不能只依賴主鍵的一部分。如果知道主鍵的所有屬性的值,就可以檢索到任何元組(行)的任何屬性的任何值。(要求中的主鍵,其實可以拓展替換為候選鍵)。對於非主屬性來說,並非完全依賴候選鍵。會產生資料冗餘、插入異常、刪除異常、更新異常等問題。
1NF告訴我們欄位屬性需要是原子性的,而2NF告訴我們一張表就是一個獨立的物件,一張表只表達一個意思。
第二正規化(2NF)要求實體的屬性完全依賴於關鍵字。如果存在不完全依賴,那麼這個屬性和關鍵字的這一部分應該分離出來形成一個新的實體,新實體與元實體之間是一對多的關係。
1.4、第三正規化
第三正規化是在第二正規化的基礎上,確保資料表中的每一個非主鍵欄位和主鍵欄位直接相關,也就是說,要求資料表中的所有非主鍵欄位不能依賴於其它非主鍵欄位。即,不能存在非主屬性A依賴於非主屬性B,非主屬性B依賴於主鍵C的情況,即存在“A➡B➡C”的決定關係。通俗的來講,該規則的意思是所有非主鍵屬性之間不能有依賴關係,必須相互獨立。這裡的主鍵可以拓展為候選鍵。
符合3NF後的資料模型通俗地講,2NF和3NF通常以這句話概括:“每個非鍵屬性依賴於鍵,依賴於整個鍵,並且除了鍵別無他物”。
1.5、反正規化化
1.5.1、反正規化化概述
有的時候不能簡單按照規範要求設計資料庫,因為有的資料看是冗餘,其實對業務來說十分重要。這個時候,我們就要遵循業務優先的原則,首先滿足業務需求,再儘量減少冗餘。如果資料庫的資料量比較大,系統的UV和PV訪問頻次比較高,則完全按照MySQL的三大正規化設計資料表,讀資料時會產生大量的關聯查詢,在一定程度上會影響資料庫的讀效能。如果我們對查詢效率進行優化,反正規化化也是一種優化思路。此時,可以通過在資料表中增加冗餘欄位來提高資料庫的讀效能。
1.5.2、反正規化的問題
- 儲存空間變大了
- 一個表中欄位做了修改,另一個表中冗餘的欄位也需要做同步修改,否則資料不一致
- 若採用儲存過程來支援資料的更新、刪除等額外操作,如果更新頻繁,會非常消耗系統資源
- 在資料量小的情況下,反正規化不能體現效能的優勢,可能還會讓資料庫的設計更加複雜
1.5.3、反正規化的適用場景
當冗餘資訊有價值或者能 大幅度提高查詢效率 的時候,我們才會採取反正規化的優化。
1.4.4、增加冗餘欄位的建議
增加冗餘欄位一定要符合如下兩個條件。只有滿足這兩個條件,才可以考慮增加冗餘欄位。
- 這個冗餘欄位不需要經常進行修改
- 這個冗餘欄位查詢的時候不可或缺
1.6、巴斯正規化(BCNF)
巴斯正規化時在第三正規化的基礎上進行了改進。若一個關係達到了第三正規化,並且它只有一個候選鍵,或者它的每個候選鍵都是單屬性,則該關係自然達到了巴斯正規化。巴斯正規化在第三正規化的基礎上消除了主屬性對候選鍵的部分依賴或者傳遞依賴關係。一般來說,一個數據庫的設計符合第三正規化或巴斯正規化就可以了。
1.7、第四正規化
第四正規化即在滿足巴斯-科德正規化的基礎上,消除非平凡且非函式依賴(即把同一表內的多對多關係刪除)。
- 多值依賴:即屬性之間的一對多關係,即為K➡➡A
- 函式依賴:事實上是單值依賴,所以不能表達屬性值之間的一對多關係
- 平凡的多值依賴:全集U=K+A+B,一個K可以對應於多個A,也可以對應於多個B,A與B互相獨立,即K➡➡A,K➡➡B。整個表有多組一對多關係,且有:“一”部分是相同的屬性集合,“多”部分是互相獨立的屬性集合。
1.8、第五正規化
在滿足第四正規化(4NF)的基礎上,消除不是由候選鍵所蘊含的連線依賴。如果關係模式R中的每一個連線依賴均由R的候選鍵所隱含,則稱此關係模式符合第五正規化。函式依賴是多值依賴的一種特殊的情況,而多值依賴實際上是連線依賴的一種特殊情況。但連線依賴不像函式依賴和多值依賴可以由語義直接匯出,而是在 關係連線運算 時才反映出來。存在連線依賴的關係模式仍可能遇到資料冗餘及插入、修改、刪除異常等問題。第五正規化處理的是 無損連線問題 ,這個正規化基本 沒有實際意義 ,因為無損連線很少出現,而且難以察覺。而域鍵正規化試圖定義一個 終極正規化 ,該正規化考慮所有的依賴和約束型別,但是實用價值也是最小的,只存在理論研究中
1.9、案例
進貨單表
listnumber (單號) |
supplierid (供應商編號) |
suppliername(供應商名稱) | stock (倉庫) |
barcode (條碼) |
goodsname (名稱) |
property (屬性) |
quantity (數量) |
importprice (進貨價格) |
importvalue (進貨金額) |
---|---|---|---|---|---|---|---|---|---|
100001 | 1 | 食品廠 | 倉庫 | 0001 | 方便麵 | 6包/袋 | 200 | 25 | 5000 |
100001 | 1 | 食品廠 | 倉庫 | 0002 | 棒棒糖 | 10支/盒 | 400 | 9.9 | 3960 |
100002 | 2 | 服裝廠 | 賣場 | 0003 | 西服 | 套 | 5 | 2000 | 10000 |
100003 | 1 | 食品廠 | 賣場 | 0002 | 棒棒糖 | 10支/盒 | 200 | 15 | 3000 |
1.9.1、迭代一次
第一正規化要求:所有的欄位都是基本資料型別,不可進一步拆分。這張表裡,我們將“property”這一欄位,拆分為“specification(規格)”和“unit(單位)”這兩個欄位。
listnumber (單號) |
supplierid (供應商編號) |
suppliername(供應商名稱) | stock (倉庫) |
barcode (條碼) |
goodsname (名稱) |
specification (規格) |
unit (單位) |
quantity (數量) |
importprice (進貨價格) |
importvalue (進貨金額) |
---|---|---|---|---|---|---|---|---|---|---|
100001 | 1 | 食品廠 | 倉庫 | 0001 | 方便麵 | 6包 | 袋 | 200 | 25 | 5000 |
100001 | 1 | 食品廠 | 倉庫 | 0002 | 棒棒糖 | 10支 | 盒 | 400 | 9.9 | 3960 |
100002 | 2 | 服裝廠 | 賣場 | 0003 | 西服 | NULL | 套 | 5 | 2000 | 10000 |
100003 | 1 | 食品廠 | 賣場 | 0002 | 棒棒糖 | 10支 | 盒 | 200 | 15 | 3000 |
1.9.2、迭代兩次
第二正規化要求,在滿足第一正規化的基礎上,還要滿足資料表裡的每一條記錄,都是可唯一標識。而且所有欄位,都必須完全依賴於主鍵,不能只依賴主鍵的一部分。
第1步,就是要確定這個表的主鍵。通過觀察發現,欄位“listnumber(單號)”+“barcode(條碼)”可以唯一標識每一條記錄,可以作為主鍵。
第2步,確定好了主鍵以後,判斷哪些欄位完全依賴於主鍵,哪些欄位只依賴於主鍵的一部分。把只依賴於主鍵的一部分的欄位拆分出去,形成新的表。
首先,進貨單明細表裡面的“goodsname(名稱)”“specification(規格)”“unit(單位)”這些資訊是商品的屬性,只依賴於“barcode(條碼)”,不完全依賴主鍵,可以拆分出去。我們將這3個欄位加上它們所依賴的欄位“barcode(條碼)”,拆分形成一個新的資料表“商品資訊表”。這樣一來,原來的資料表被拆分為兩個表。此外。欄位“supplierid(供應商編號)”“suppliername(供應商名稱)”“stock(倉庫)”只依賴於“listnumber(單號)”,不完全依賴於主鍵。所以,我們可以把這單個欄位拆分出去,在加上它們依賴的欄位“listnumber
(單號)”,就形成了一個“進貨單頭表”。剩下的欄位,會組成一個新的表,我們就叫它“進貨單明細表”。
在“商品資訊表”中,欄位“barcode”是由可能存在重複的,比如,使用者門店可能由散裝稱重的商品和自產商品,會存在條碼共用的情況。所以,所有的欄位都不能唯一標識表裡的記錄。這個時候,我們必須給這個表加上一個主鍵,比如說是自增欄位“itemnumber”
商品資訊表
itemnumber | barcode (條碼) |
goodsname (名稱) |
specification (規格) |
unit (單位) |
---|---|---|---|---|
1 | 0001 | 方便麵 | 6包 | 袋 |
2 | 0002 | 棒棒糖 | 10支 | 盒 |
3 | 0003 | 西服 | NULL | 套 |
4 | 0002 | 棒棒糖 | 10支 | 盒 |
進貨單頭表
listnumber (單號) |
supplierid (供應商編號) |
suppliername(供應商名稱) | stock (倉庫) |
---|---|---|---|
100001 | 1 | 食品廠 | 倉庫 |
100002 | 2 | 服裝廠 | 賣場 |
100003 | 1 | 食品廠 | 賣場 |
進貨單明細表
listnumber (單號) |
itemnumber | barcode (條碼) |
quantity (數量) |
importprice (進貨價格) |
importvalue (進貨金額) |
---|---|---|---|---|---|
100001 | 1 | 0001 | 200 | 25 | 5000 |
100001 | 2 | 0002 | 400 | 9.9 | 3960 |
100002 | 3 | 0003 | 5 | 2000 | 10000 |
100003 | 4 | 0002 | 200 | 15 | 3000 |
1.9.3、迭代三次
第三正規化是在第二正規化的基礎上,確保資料表中的每一個非主鍵欄位和主鍵欄位直接相關,也就是說,要求資料表中的所有非主鍵欄位不能依賴於其它非主鍵欄位。
供貨商表
supplierid (供應商編號) |
suppliername(供應商名稱) |
---|---|
1 | 食品廠 |
2 | 服裝廠 |
進貨單頭表
listnumber (單號) |
supplierid (供應商編號) |
stock (倉庫) |
---|---|---|
100001 | 1 | 倉庫 |
100002 | 2 | 賣場 |
100003 | 1 | 賣場 |
商品資訊表
itemnumber | barcode (條碼) |
goodsname (名稱) |
specification (規格) |
unit (單位) |
---|---|---|---|---|
1 | 0001 | 方便麵 | 6包 | 袋 |
2 | 0002 | 棒棒糖 | 10支 | 盒 |
3 | 0003 | 西服 | NULL | 套 |
4 | 0002 | 棒棒糖 | 10支 | 盒 |
進貨單明細表
listnumber (單號) |
itemnumber | barcode (條碼) |
quantity (數量) |
importprice (進貨價格) |
importvalue (進貨金額) |
---|---|---|---|---|---|
100001 | 1 | 0001 | 200 | 25 | 5000 |
100001 | 2 | 0002 | 400 | 9.9 | 3960 |
100002 | 3 | 0003 | 5 | 2000 | 10000 |
100003 | 4 | 0002 | 200 | 15 | 3000 |
二、ER模型
2.1、ER模型概述
ER模型也叫做關係實體模型,用來描述現實生活中客觀存在的事物、事物的屬性、以及事物之間關係的一種資料模型。在開發基於資料庫的資訊系統的設計階段,通常使用ER模型來描述資訊需求和資訊特性,幫助我們業務邏輯,從而設計出優秀的資料庫。
ER模型由三個要素,分別是實體、屬性和關係。
實體,可以看做是資料物件,往往對應於現實生活中的真實存在的個體。在 ER 模型中,用 矩形 來表示。實體分為兩類,分別是強實體和弱實體。強實體是指不依賴於其他實體的實體;弱實體是指對另一個實體有很強的依賴關係的實體。
屬性,則是指實體的特性。比如超市的地址、聯絡電話、員工數等。在 ER 模型中用橢圓形來表示。
關係,則是指實體之間的聯絡。比如超市把商品賣給顧客,就是一種超市與顧客之間的聯絡。在 ER 模型中用菱形來表示。
2.2、關係的型別
在 ER 模型的 3 個要素中,關係又可以分為 3 種類型,分別是一對一、一對多、多對多。
一對一:指實體之間的關係是一一對應的,比如個人與身份證資訊之間的關係就是一對一的關係。一個人只能有一個身份證資訊,一個身份證資訊也只屬於一個人。
一對多:指一邊的實體通過關係,可以對應多個另外一邊的實體。相反,另外一邊的實體通過這個關係,則只能對應唯一的一邊的實體。比如說,我們新建一個班級表,而每個班級都有多個學生,每個學生則對應一個班級,班級對學生就是一對多的關係。
多對多:指關係兩邊的實體都可以通過關係對應多個對方的實體。比如在進貨模組中,供貨商與超市之間的關係就是多對多的關係,一個供貨商可以給多個超市供貨,一個超市也可以從多個供貨商那裡採購商品。再比如一個選課表,有許多科目,每個科目有很多學生選,而每個學生又可以選擇多個科目,這就是多對多的關係。
2.3、ER 模型圖轉換成資料表
- 一個實體通常轉換成一個資料表;
- 一個多對多的關係,通常也轉換成一個資料表;
- 一個1對1,或者1對多的關係,往往通過表的外來鍵來表達,而不是設計一個新的資料表;
- 屬性轉換成表的欄位。
三、資料表設計原則
- 資料表的個數越少越好
- RDMS的核心在於對實體和聯絡的定義,也就是 E-R 圖(Entity Relationship Diagram),資料表越少,證明實體和聯絡設計得越簡潔,既方便理解又方便操作。
- 資料表中的欄位個數越少越好
- 欄位個數越多,資料冗餘的可能性越大。設定欄位個數少的前提是各個欄位相互獨立,而不是某個欄位的取值可以由其它欄位計算出來。當然欄位個數少是相對的,我們通常會在資料冗餘和檢索效率中進行平衡。
- 資料表中聯合主鍵的欄位個數越少越好
- 設定主鍵是為了確定唯一性,當一個欄位無法確定唯一性的時候,就需要採用聯合主鍵的方式(也就是用多個欄位來定義一個主鍵)。聯合主鍵中的欄位越多,佔用的索引空間越大,不僅會加大理解難度,還會增加執行時間和索引空間,因此聯合主鍵的欄位個數越少越好。
- 使用主鍵和外來鍵越多越好
- 資料庫的設計實際上就是定義各種表,以及各種欄位之間的關係。這些關係越多,證明這些實體之間的冗餘度越低,利用度越高。這樣做的好處在於不僅保證了資料表之間的獨立性,還能提升相互之間的關聯使用率。