1. 程式人生 > >為什麼我不喜歡資料庫三正規化

為什麼我不喜歡資料庫三正規化

目錄

  • 插曲
  • 三正規化的定義
  • 為什麼需要正規化
  • 反正規化設計

插曲

最近,一個遠房親戚的小表弟準備選修專業
找到我問:

"哥,現在學資料庫有沒有前途阿?"

"當然有啊,前途大大的呢"
"那我現在開始學資料庫,需要先從什麼開始呢?"

"學課程的話,先了解下資料庫三正規化,SQL這些吧"

"SQL我大概知道,資料庫三正規化是什麼?"
"阿...三正規化就是表的主鍵...唯一性那些東西吧,...嗯,應該就是那些"

"什麼是主鍵?"

"額.....表弟你不要再問了啦,好好去百度一下行不。"

"噢...."

掛完電話,我舒了口氣,由於差點暴露自己已經不記得三正規化了這個不爭的事實,我悄悄打開了谷歌....

資料庫的這個三正規化的概念,相信大多數人都不會陌生,從懵懵懂懂的大學時代就已經普及到教材了(沒記錯的話應該在資料庫系統概論這本教材裡)。
還記得那會剛開始找實習的時候,由於自己本事太小,連簡歷都不知道怎麼寫好,尤其是擅長技術的部分更是一片空白。
於是乎會找來隔壁幾個學霸的簡歷來做參考,那會兒大家的簡歷上都會赫赫寫著:

熟練掌握資料庫三正規化,精通資料庫系統開發語言。

又或者是:
熟悉ER圖製作工具,能實現滿足三正規化的資料庫設計

一開始覺得資料庫三正規化確實是個好東西,以至於面試的時候技術官沒有提問到三正規化的細節,自己感到了驚訝和茫然。
隨著工作經驗逐漸見長,資料庫正規化理論在腦海裡的強印象漸漸消除。我在想,要麼是記憶的衰退,要麼就是有些原則已經形成了本能的經驗了。

那麼,什麼是資料庫的正規化?

三正規化的定義

這裡,不想花太多的篇幅去討論理論性的東西,這些資訊一抓一大把。我們就通過一些簡單的例子來體會一下。

1. 第一正規化

假設有一張使用者資訊表,上面除了使用者編號、姓名之外,還會記錄地址資訊:

編號 姓名 性別 所在地
0001 張三 廣東省,深圳市
0002 李四 海南省,海口市

在這裡面,地址資訊一欄就是不符合第一正規化(1NF)的:
第一正規化(1NF):資料庫表的每一列都是不可分割的原子項

因此,應該拆分為:

編號 姓名 性別 所在省 地市
0001 張三 廣東省 深圳市
0002 李四 海南省 海口市

2. 第二正規化

以一個訂單表為例,通常在淘寶上下單時會產生包含多個商品的訂單,如下:

訂單號 商品號 商品名稱 價格
o1 g1 洗衣液 23
o1 g2 吹風機 125
o1 g3 蠶豆 5
o2 g9 被子 302
o2 g8 枕頭 69

這裡同樣違反了第二正規化的定義:
第二正規化(2NF):每個表必須有且僅有一個數據元素為主鍵(Primary key),其他屬性需完全依賴於主鍵

第二正規化需建立在滿足第一正規化的基礎之上

第二正規化首先要求的是存在一個唯一的主鍵,在上面的表中,就必須將 訂單號、商品號 作為一個聯合的主鍵才能滿足要求。
那麼對於第二點要求呢? 其他屬性是否依賴於這個主鍵?
在訂單的場景中,我們可以認為這算是合理的,因為商品的價格甚至名稱都可能會發生變化,而在每個訂單中所看到的這些資訊都應該是不變的,
誰也不希望看到自己已經支付的訂單中的商品資訊突然大降價.. 當然更重要的還是保持訂單總價與商品單價記錄的一致性。
因此這裡的記錄可以認為是商品資訊在建立訂單時的一個快照。

但是,對於下面的這一場景可能就不合適了:

訂單號 商品號 商品名稱 價格
o1 g1 洗衣液 23
o1 g2 吹風機 125
o1 g3 蠶豆 5
o2 g9 被子 302
o2 g8 枕頭 69

商品所屬的類別一般是固定的,也就是商品的類別屬性僅僅與商品編號相關,即僅僅是依賴於主鍵的一部分。
這就違反了第二正規化中"其他屬性必須完全依賴於主鍵"的規則,因此需要將該屬性分離到商品資訊表中。

3. 第三正規化

讓我們回到一開始的使用者表,如果在使用者資訊表中,同時補充一些城市的資訊:

編號 姓名 性別 城市 城市特色
0001 張三 深圳市 科技、創新
0002 李四 海口市 旅遊、觀光

這樣便違反了第三正規化的定義:

第三正規化(3NF):資料表中的每一列都和主鍵直接相關,而不能間接相關

同樣,第三正規化也需要建立在第二正規化的基礎之上

很明顯,這裡的城市人口、特色等屬性都僅僅依賴於使用者所在的城市,而不是使用者,只能算間接的關係。
因此最好的做法是將城市相關的屬性分離到一個城市資訊表中。

為什麼需要正規化

資料庫正規化為資料庫的設計、開發提供了一個可參考的典範,在許多教學材料中也是作為關鍵的課程內容。
那麼正規化的提出是為了解決什麼問題?

  • 第一正規化,要求將列儘可能最小的分割,希望消除某個列儲存多個值的冗餘的行為
    比如使用者表中的地址資訊,拆分為省、市這種明確的欄位,可以按獨立的欄位檢索、查詢

  • 第二正規化,要求唯一的主鍵,且不存在對主鍵的部分依賴,希望消除表中存在冗餘(多餘)的列
    比如訂單表中的商品分類、詳情資訊,只需要由商品資訊表儲存一份即可。

  • 第三正規化,要求沒有間接依賴於主鍵的列,即仍然是希望消除表中冗餘的列
    比如使用者表中不需要儲存額外的 其所在城市的人口、城市特點等資訊。

很明顯,這些正規化大都是為了消除冗餘而提出的,即儘可能的減少儲存成本。

PS:你懂得三正規化,可以幫老闆省錢,難怪簡歷上要寫上..

除了本文中提到的三正規化之外,實質上還有BCNF正規化、第四、第五正規化。

藉助三正規化的理念,你可以設計出很精煉的資料庫表結構。然而現有的專案應用並不會完全遵循正規化的理念,原因在於:

  1. 效能原因,沒有任何冗餘的表設計會產生更多的查詢行為,這意味著會產生更多次的資料庫IO操作。在一些實時互動的系統中,可能會慢得讓人難以忍受。
    當然,你可以使用資料庫的 連線(join) 操作,而事實上資料庫提供 join 也就是為了來緩解這種問題。但一旦用到了分庫分表方案的面前,這個問題就會非常的棘手。

  2. 成本結構的變化,資料庫正規化是在20世紀提出的,當時的磁碟儲存成本還很高。隨著科技發展,資料儲存的成本已經大幅度縮減,對於採用正規化設計(規避冗餘)帶來的成本縮減收益已經不那麼明顯。

反正規化設計

既然正規化是為了消除冗餘,那麼反正規化就是通過增加冗餘、聚合的手段來提升效能。比如,為了提升查詢的效能,在CMS的文章表中同時冗餘作者的資訊。
當然,除了冗餘(儲存多份拷貝) 之外,還有另外的理念,即資料的聚合,或者叫巢狀。這種做法相當於是將多個欄位(列)合併儲存到資料庫表的一個列中。

比如一條訂單資料就可以同時包含許多資訊:

{
 "oid": "0001",
 "price": {
  "total": 380,
  "benefit": 40
 },

 "goods": [{
   "gid": "SN001",
   "name": "藍月亮洗衣液",
   "price": 41,
   "amount": 2
  },
  {
   "gid": "SN003",
   "name": "電動剃鬚刀",
   "price": 99,
   "amount": 1
  }
 ],

 "address": {
  "contact": "張三",
  "phone": "150899000"
   ...
 }
...
}

這種靈活的結構幾乎是 NoSQL的專利,比如MongoDB文件資料庫就可以直接以內嵌陣列、物件的形式來實現聚合式儲存,這無疑帶來了極大的靈活性。
而 MySQL 在5.2.7版本開始支援JSON結構化列,也進入了聚合式儲存的隊伍,與其對標的PostGreSQL 則是9.4版本就已經支援。

反正規化的設計在網際網路專案、開源產品中也非常之常見,比如大名鼎鼎的Discuz 的資料表設計中就存在許多的冗餘列、聚合欄位。
一方面,除了能獲得性能的提升之外,資料壓縮、高度靈活擴充套件(非結構化) 也是反正規化設計能獲得青睞的理由。

當然,這裡並非一律反對資料庫正規化,理解正規化仍然是做好資料庫設計的一門基礎,比如選擇合適的主鍵、清晰的劃分每一列屬性等等。
在專案中仍然需要根據自身的業務特點在正規化和反正規化中找到平衡點(通常是兩者的結合)。類似於架構設計中空間換時間的一些做法,這其中涉及到的各種取捨都是需要經過權衡的。

也可以說這是一門藝術,因為沒有標準答