为什么我不喜欢数据库三范式
插曲
最近,一個(gè)遠(yuǎn)房親戚的小表弟準(zhǔn)備選修專業(yè)
找到我問:
掛完電話,我舒了口氣,由于差點(diǎn)暴露自己已經(jīng)不記得三范式了這個(gè)不爭的事實(shí),我悄悄打開了谷歌....
數(shù)據(jù)庫的這個(gè)三范式的概念,相信大多數(shù)人都不會(huì)陌生,從懵懵懂懂的大學(xué)時(shí)代就已經(jīng)普及到教材了(沒記錯(cuò)的話應(yīng)該在數(shù)據(jù)庫系統(tǒng)概論這本教材里)。
還記得那會(huì)剛開始找實(shí)習(xí)的時(shí)候,由于自己本事太小,連簡歷都不知道怎么寫好,尤其是擅長技術(shù)的部分更是一片空白。
于是乎會(huì)找來隔壁幾個(gè)學(xué)霸的簡歷來做參考,那會(huì)兒大家的簡歷上都會(huì)赫赫寫著:
熟練掌握數(shù)據(jù)庫三范式,精通數(shù)據(jù)庫系統(tǒng)開發(fā)語言。
又或者是:
熟悉ER圖制作工具,能實(shí)現(xiàn)滿足三范式的數(shù)據(jù)庫設(shè)計(jì)
一開始覺得數(shù)據(jù)庫三范式確實(shí)是個(gè)好東西,以至于面試的時(shí)候技術(shù)官?zèng)]有提問到三范式的細(xì)節(jié),自己感到了驚訝和茫然。
隨著工作經(jīng)驗(yàn)逐漸見長,數(shù)據(jù)庫范式理論在腦海里的強(qiáng)印象漸漸消除。我在想,要么是記憶的衰退,要么就是有些原則已經(jīng)形成了本能的經(jīng)驗(yàn)了。
那么,什么是數(shù)據(jù)庫的范式?
三范式的定義
這里,不想花太多的篇幅去討論理論性的東西,這些信息一抓一大把。我們就通過一些簡單的例子來體會(huì)一下。
1. 第一范式
假設(shè)有一張用戶信息表,上面除了用戶編號(hào)、姓名之外,還會(huì)記錄地址信息:
| 0001 | 張三 | 男 | 廣東省,深圳市 |
| 0002 | 李四 | 女 | 海南省,海口市 |
在這里面,地址信息一欄就是不符合第一范式(1NF)的:
第一范式(1NF):數(shù)據(jù)庫表的每一列都是不可分割的原子項(xiàng)
因此,應(yīng)該拆分為:
| 0001 | 張三 | 男 | 廣東省 | 深圳市 |
| 0002 | 李四 | 女 | 海南省 | 海口市 |
2. 第二范式
以一個(gè)訂單表為例,通常在淘寶上下單時(shí)會(huì)產(chǎn)生包含多個(gè)商品的訂單,如下:
| o1 | g1 | 洗衣液 | 23 |
| o1 | g2 | 吹風(fēng)機(jī) | 125 |
| o1 | g3 | 蠶豆 | 5 |
| o2 | g9 | 被子 | 302 |
| o2 | g8 | 枕頭 | 69 |
這里同樣違反了第二范式的定義:
第二范式(2NF):每個(gè)表必須有且僅有一個(gè)數(shù)據(jù)元素為主鍵(Primary key),其他屬性需完全依賴于主鍵
第二范式需建立在滿足第一范式的基礎(chǔ)之上
第二范式首先要求的是存在一個(gè)唯一的主鍵,在上面的表中,就必須將 訂單號(hào)、商品號(hào) 作為一個(gè)聯(lián)合的主鍵才能滿足要求。
那么對(duì)于第二點(diǎn)要求呢?其他屬性是否依賴于這個(gè)主鍵?
在訂單的場景中,我們可以認(rèn)為這算是合理的,因?yàn)樯唐返膬r(jià)格甚至名稱都可能會(huì)發(fā)生變化,而在每個(gè)訂單中所看到的這些信息都應(yīng)該是不變的,
誰也不希望看到自己已經(jīng)支付的訂單中的商品信息突然大降價(jià).. 當(dāng)然更重要的還是保持訂單總價(jià)與商品單價(jià)記錄的一致性。
因此這里的記錄可以認(rèn)為是商品信息在創(chuàng)建訂單時(shí)的一個(gè)快照。
但是,對(duì)于下面的這一場景可能就不合適了:
| o1 | g1 | 洗衣液 | 23 | 家居 |
| o1 | g2 | 吹風(fēng)機(jī) | 125 | 電器 |
| o1 | g3 | 蠶豆 | 5 | 食品 |
| o2 | g9 | 被子 | 302 | 家居 |
| o2 | g8 | 枕頭 | 69 | 家居 |
商品所屬的類別一般是固定的,也就是商品的類別屬性僅僅與商品編號(hào)相關(guān),即僅僅是依賴于主鍵的一部分。
這就違反了第二范式中"其他屬性必須完全依賴于主鍵"的規(guī)則,因此需要將該屬性分離到商品信息表中。
3. 第三范式
讓我們回到一開始的用戶表,如果在用戶信息表中,同時(shí)補(bǔ)充一些城市的信息:
| 0001 | 張三 | 男 | 深圳市 | 科技、創(chuàng)新 | 1300W |
| 0002 | 李四 | 女 | 海口市 | 旅游、觀光 | 230W |
這樣便違反了第三范式的定義:
第三范式(3NF):數(shù)據(jù)表中的每一列都和主鍵直接相關(guān),而不能間接相關(guān)
同樣,第三范式也需要建立在第二范式的基礎(chǔ)之上
很明顯,這里的城市人口、特色等屬性都僅僅依賴于用戶所在的城市,而不是用戶,只能算間接的關(guān)系。
因此最好的做法是將城市相關(guān)的屬性分離到一個(gè)城市信息表中。
為什么需要范式
數(shù)據(jù)庫范式為數(shù)據(jù)庫的設(shè)計(jì)、開發(fā)提供了一個(gè)可參考的典范,在許多教學(xué)材料中也是作為關(guān)鍵的課程內(nèi)容。
那么范式的提出是為了解決什么問題?
第一范式,要求將列盡可能最小的分割,希望消除某個(gè)列存儲(chǔ)多個(gè)值的冗余的行為
比如用戶表中的地址信息,拆分為省、市這種明確的字段,可以按獨(dú)立的字段檢索、查詢第二范式,要求唯一的主鍵,且不存在對(duì)主鍵的部分依賴,希望消除表中存在冗余(多余)的列
比如訂單表中的商品分類、詳情信息,只需要由商品信息表存儲(chǔ)一份即可。第三范式,要求沒有間接依賴于主鍵的列,即仍然是希望消除表中冗余的列
比如用戶表中不需要存儲(chǔ)額外的 其所在城市的人口、城市特點(diǎn)等信息。
很明顯,這些范式大都是為了消除冗余而提出的,即盡可能的減少存儲(chǔ)成本。
PS:你懂得三范式,可以幫老板省錢,難怪簡歷上要寫上..
除了本文中提到的三范式之外,實(shí)質(zhì)上還有BCNF范式、第四、第五范式。
借助三范式的理念,你可以設(shè)計(jì)出很精煉的數(shù)據(jù)庫表結(jié)構(gòu)。然而現(xiàn)有的項(xiàng)目應(yīng)用并不會(huì)完全遵循范式的理念,原因在于:
性能原因,沒有任何冗余的表設(shè)計(jì)會(huì)產(chǎn)生更多的查詢行為,這意味著會(huì)產(chǎn)生更多次的數(shù)據(jù)庫IO操作。在一些實(shí)時(shí)交互的系統(tǒng)中,可能會(huì)慢得讓人難以忍受。
當(dāng)然,你可以使用數(shù)據(jù)庫的 連接(join) 操作,而事實(shí)上數(shù)據(jù)庫提供 join 也就是為了來緩解這種問題。但一旦用到了分庫分表方案的面前,這個(gè)問題就會(huì)非常的棘手。
成本結(jié)構(gòu)的變化,數(shù)據(jù)庫范式是在20世紀(jì)提出的,當(dāng)時(shí)的磁盤存儲(chǔ)成本還很高。隨著科技發(fā)展,數(shù)據(jù)存儲(chǔ)的成本已經(jīng)大幅度縮減,對(duì)于采用范式設(shè)計(jì)(規(guī)避冗余)帶來的成本縮減收益已經(jīng)不那么明顯。
反范式設(shè)計(jì)
既然范式是為了消除冗余,那么反范式就是通過增加冗余、聚合的手段來提升性能。比如,為了提升查詢的性能,在CMS的文章表中同時(shí)冗余作者的信息。
當(dāng)然,除了冗余(存儲(chǔ)多份拷貝) 之外,還有另外的理念,即數(shù)據(jù)的聚合,或者叫嵌套。這種做法相當(dāng)于是將多個(gè)字段(列)合并存儲(chǔ)到數(shù)據(jù)庫表的一個(gè)列中。
比如一條訂單數(shù)據(jù)就可以同時(shí)包含許多信息:
這種靈活的結(jié)構(gòu)幾乎是 NoSQL的專利,比如MongoDB文檔數(shù)據(jù)庫就可以直接以內(nèi)嵌數(shù)組、對(duì)象的形式來實(shí)現(xiàn)聚合式存儲(chǔ),這無疑帶來了極大的靈活性。
而 MySQL 在5.2.7版本開始支持JSON結(jié)構(gòu)化列,也進(jìn)入了聚合式存儲(chǔ)的隊(duì)伍,與其對(duì)標(biāo)的PostGreSQL 則是9.4版本就已經(jīng)支持。
反范式的設(shè)計(jì)在互聯(lián)網(wǎng)項(xiàng)目、開源產(chǎn)品中也非常之常見,比如大名鼎鼎的Discuz 的數(shù)據(jù)表設(shè)計(jì)中就存在許多的冗余列、聚合字段。
一方面,除了能獲得性能的提升之外,數(shù)據(jù)壓縮、高度靈活擴(kuò)展(非結(jié)構(gòu)化) 也是反范式設(shè)計(jì)能獲得青睞的理由。
當(dāng)然,這里并非一律反對(duì)數(shù)據(jù)庫范式,理解范式仍然是做好數(shù)據(jù)庫設(shè)計(jì)的一門基礎(chǔ),比如選擇合適的主鍵、清晰的劃分每一列屬性等等。
在項(xiàng)目中仍然需要根據(jù)自身的業(yè)務(wù)特點(diǎn)在范式和反范式中找到平衡點(diǎn)(通常是兩者的結(jié)合)。類似于架構(gòu)設(shè)計(jì)中空間換時(shí)間的一些做法,這其中涉及到的各種取舍都是需要經(jīng)過權(quán)衡的。
也可以說這是一門藝術(shù),因?yàn)闆]有標(biāo)準(zhǔn)答案...
總結(jié)
以上是生活随笔為你收集整理的为什么我不喜欢数据库三范式的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Apollo 配置中心:分布式部署
- 下一篇: 基于SQLite+EF6实现一套自己的K