爲何我不喜歡數據庫三範式

插曲

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

"哥,如今學數據庫有沒有前途阿?"

"固然有啊,前途大大的呢"
"那我如今開始學數據庫,須要先從什麼開始呢?"

"學課程的話,先了解下數據庫三範式,SQL這些吧"

"SQL我大概知道,數據庫三範式是什麼?"
"阿...三範式就是表的主鍵...惟一性那些東西吧,...嗯,應該就是那些"

"什麼是主鍵?"

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

"噢...."

掛完電話,我舒了口氣,因爲差點暴露本身已經不記得三範式了這個不爭的事實,我悄悄打開了谷歌....數組

數據庫的這個三範式的概念,相信大多數人都不會陌生,從懵懵懂懂的大學時代就已經普及到教材了(沒記錯的話應該在數據庫系統概論這本教材裏)。
還記得那會剛開始找實習的時候,因爲本身本事過小,連簡歷都不知道怎麼寫好,尤爲是擅長技術的部分更是一片空白。
因而乎會找來隔壁幾個學霸的簡從來作參考,那會兒你們的簡歷上都會赫赫寫着:架構

熟練掌握數據庫三範式,精通數據庫系統開發語言。數據庫設計

又或者是:
熟悉ER圖製做工具,能實現知足三範式的數據庫設計工具

一開始以爲數據庫三範式確實是個好東西,以致於面試的時候技術官沒有提問到三範式的細節,本身感到了驚訝和茫然。
隨着工做經驗逐漸見長,數據庫範式理論在腦海裏的強印象漸漸消除。我在想,要麼是記憶的衰退,要麼就是有些原則已經造成了本能的經驗了。性能

那麼,什麼是數據庫的範式?架構設計

三範式的定義

這裏,不想花太多的篇幅去討論理論性的東西,這些信息一抓一大把。咱們就經過一些簡單的例子來體會一下。設計

1. 第一範式

假設有一張用戶信息表,上面除了用戶編號、姓名以外,還會記錄地址信息:code

編號 姓名 性別 所在地
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 張三 深圳市 科技、創新 1300W
0002 李四 海口市 旅遊、觀光 230W

這樣便違反了第三範式的定義:

第三範式(3NF):數據表中的每一列都和主鍵直接相關,而不能間接相關

一樣,第三範式也須要創建在第二範式的基礎之上

很明顯,這裏的城市人口、特點等屬性都僅僅依賴於用戶所在的城市,而不是用戶,只能算間接的關係。
所以最好的作法是將城市相關的屬性分離到一個城市信息表中。

爲何須要範式

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

  • 第一範式,要求將列儘量最小的分割,但願消除某個列存儲多個值的冗餘的行爲
    好比用戶表中的地址信息,拆分爲省、市這種明確的字段,能夠按獨立的字段檢索、查詢

  • 第二範式,要求惟一的主鍵,且不存在對主鍵的部分依賴,但願消除表中存在冗餘(多餘)的列
    好比訂單表中的商品分類、詳情信息,只須要由商品信息表存儲一份便可。

  • 第三範式,要求沒有間接依賴於主鍵的列,即仍然是但願消除表中冗餘的列
    好比用戶表中不須要存儲額外的 其所在城市的人口、城市特色等信息。

很明顯,這些範式大都是爲了消除冗餘而提出的,這有利於數據的一致性,固然也能夠儘量的減小存儲成本。

PS:你懂得三範式,能夠幫老闆省錢,難怪簡歷上要寫上..

除了本文中提到的三範式以外,實質上還有BCNF範式、第4、第五範式。

藉助三範式的理念,你能夠設計出很精煉的數據庫表結構。然而現有的項目應用並不會徹底遵循範式的理念,緣由好比:

  1. 性能緣由,沒有任何冗餘的表設計會產生更多的查詢行爲,這意味着會產生更屢次的數據庫IO操做。在一些實時交互的系統中,可能會慢得讓人難以忍受。
    固然,你可使用數據庫的 鏈接(join) 操做,而事實上數據庫提供 join 也就是爲了來緩解這種問題。但一旦用到了分庫分表方案,這個問題就會很是的棘手。

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

反範式設計

既然範式是爲了消除冗餘,那麼反範式就是經過增長冗餘、聚合的手段來提高性能。好比,爲了提高查詢的性能,在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.7.2版本開始支持JSON結構化列,也進入了聚合式存儲的隊伍,與其對標的PostGreSQL 則是9.4版本就已經支持。

反範式的作法在互聯網項目、開源產品中也比較常見,好比Discuz 的數據表設計中就存在許多的冗餘列、聚合字段。
一方面,除了能得到性能的提高以外,數據壓縮、高度靈活擴展(非結構化) 也是反範式設計能得到青睞的理由。

固然,這裏並不是一概反對數據庫範式,理解範式仍然是作好數據庫設計的一門基礎,好比選擇合適的主鍵、清晰的劃分每一列屬性等等。
在項目中仍然須要根據自身的業務特色在範式和反範式中找到平衡點(一般是二者的結合)。相似於架構設計中空間換時間的一些作法,這其中涉及到的各類取捨都是須要通過權衡的。

也能夠說這是一門「藝術「,由於沒有標準答案...

相關文章
相關標籤/搜索