1. 前言
在面向?qū)ο笳Z(yǔ)言中涉及到諸多的設(shè)計(jì)模式,例如單例模式、適配器模式,設(shè)計(jì)模式的存在是為了讓系統(tǒng)中的代碼邏輯更加清晰,幫助開發(fā)者建立更加健壯的系統(tǒng),同時(shí)滿足易修改特性和易擴(kuò)展特性。數(shù)據(jù)庫(kù)設(shè)計(jì)時(shí)也存在類似設(shè)計(jì)模式的通用規(guī)范,被稱為數(shù)據(jù)庫(kù)范式。滿足范式的數(shù)據(jù)庫(kù)是簡(jiǎn)潔的,表與表之間的關(guān)系也清晰且明確,不會(huì)存儲(chǔ)過(guò)多的冗余信息,在增刪改查的時(shí)候也可以避免冗余的操作。
2. 數(shù)據(jù)庫(kù)設(shè)計(jì)三大范式
面試官提問(wèn): 請(qǐng)描述下數(shù)據(jù)庫(kù)設(shè)計(jì)的三大范式?
題目解析: 回答本題時(shí),可以從總分的結(jié)構(gòu)來(lái)闡述,即先闡述數(shù)據(jù)庫(kù)范式的定義,再挨個(gè)解釋每種范式的設(shè)計(jì)原則。
數(shù)據(jù)庫(kù)范式定義:為了建立邏輯結(jié)構(gòu)合理、冗余較小的數(shù)據(jù)庫(kù),在設(shè)計(jì)數(shù)據(jù)表時(shí)必須要遵循的設(shè)計(jì)規(guī)范。
接下來(lái)可以分點(diǎn)闡述第一、第二、第三范式的定義和案例。
2.1 數(shù)據(jù)庫(kù)第一范式(1NF)
數(shù)據(jù)庫(kù)第一范式是設(shè)計(jì)數(shù)據(jù)庫(kù)時(shí)需要滿足的最基本范式:
① 定義:第一范式(First Normal Form)要求數(shù)據(jù)庫(kù)表中的所有字段都是不可拆分的原子字段,換句話說(shuō),每個(gè)字段不可以再進(jìn)行拆分。
② 案例解釋:對(duì)于一張最簡(jiǎn)單的用戶信息表,定義了用戶編號(hào)、姓名、年齡、電話這三個(gè)字段,user_info表如下:
用戶編號(hào)(user_id) | 姓名(username) | 年齡(age) | 電話(phone) |
---|---|---|---|
1 | 小明 | 20 | 10086 |
2 | 小紅 | 21 | 10087 |
3 | 小王 | 22 | 10088 |
其中電話(phone)這個(gè)字段可能存儲(chǔ)的是座機(jī)電話號(hào)碼、也可能是手機(jī)電話號(hào)碼,定義上并不明確,這就違背了第一范式的原子性。所以為了滿足第一范式,我們可以將電話字段拆分為座機(jī)電話(fixed_phone)和手機(jī)電話(cell_phone)兩個(gè)字段,拆分后的user_info表如下:
用戶編號(hào)(user_id) | 姓名(username) | 年齡(age) | 座機(jī)電話(fixed_phone) | 手機(jī)電話(cell_phone) |
---|---|---|---|---|
1 | 小明 | 20 | 10086 | 18010002000 |
2 | 小紅 | 21 | 10087 | 18010002001 |
3 | 小王 | 22 | 10088 | 18010002002 |
③ 范式優(yōu)點(diǎn):拆分之后,字段定義定義清晰。在查詢數(shù)據(jù)庫(kù)時(shí)我們可以明確過(guò)濾的是座機(jī)號(hào)碼還是手機(jī)號(hào)碼,方便業(yè)務(wù)層邏輯開發(fā),而且后續(xù)維護(hù)也方便。
2.2 數(shù)據(jù)庫(kù)第二范式(2NF)
在滿足第一范式的基礎(chǔ)上,數(shù)據(jù)庫(kù)第二范式對(duì)字段定義進(jìn)行了更嚴(yán)格的約束:
① 定義:第二范式(Second Normal Form)要求數(shù)據(jù)庫(kù)中的每一列都和主鍵相關(guān),不能和主鍵的一部分相關(guān)。
② 案例解釋:在電商環(huán)境下,我們需要設(shè)計(jì)一個(gè)訂單表,因?yàn)橛唵魏蜕唐方壎ǎ?所以將商品編號(hào)和訂單編號(hào)作為訂單表的聯(lián)合主鍵,初始設(shè)計(jì)的訂單(order)表如下:
訂單編號(hào)(order_id) | 商品編號(hào)(good_id) | 購(gòu)買數(shù)量(order_num) | 單位(unit) | 商品單價(jià)(good_price) | 購(gòu)買時(shí)間(purchase_time) |
---|---|---|---|---|---|
10001 | 8888 | 1 | 千克 | 100 | 2020-10-11 |
10002 | 8888 | 1 | 千克 | 100 | 2020-10-12 |
10003 | 8890 | 3 | 克 | 300 | 2020-10-13 |
仔細(xì)觀察,我們就能發(fā)現(xiàn)這種設(shè)計(jì)的問(wèn)題在于:good_id = 8888的商品,對(duì)于order_id = 10001和10002記錄都存儲(chǔ)了相同的單位和商品價(jià)格,這種冗余存儲(chǔ)在數(shù)據(jù)量大的場(chǎng)景下是不能接收的,并且違反了第二范式設(shè)計(jì)原則,商品價(jià)格只和商品編號(hào)有關(guān),和訂單編號(hào)無(wú)關(guān),我們將這張表進(jìn)行拆分:
拆分的原則是:將屬于商品的信息單獨(dú)提煉為一張商品表,在原有的訂單表只保留商品編號(hào)作為聯(lián)合查詢時(shí)的查詢依據(jù),優(yōu)化后的訂單(order)表如下:
訂單編號(hào)(order_id) | 商品編號(hào)(good_id) | 購(gòu)買數(shù)量(order_num) | 購(gòu)買時(shí)間(purchase_time) |
---|---|---|---|
10001 | 8888 | 1 | 2020-10-11 |
10002 | 8888 | 1 | 2020-10-12 |
10003 | 8889 | 3 | 2020-10-13 |
單獨(dú)拆分出的商品(good)表如下:
商品編號(hào)(good_id) | 單位(unit) | 商品單價(jià)(good_price) |
---|---|---|
8888 | 千克 | 100 |
8889 | 克 | 300 |
③ 范式優(yōu)點(diǎn):拆分之后,降低了數(shù)據(jù)庫(kù)的冗余存儲(chǔ),并且邏輯清晰,要查詢商品信息即走good表,要查詢訂單信息即走order表。
2.3 數(shù)據(jù)庫(kù)第三范式(3NF)
① 定義:第三范式(Third Normal Form)要求數(shù)據(jù)庫(kù)表中的每個(gè)字段和主鍵都直接相關(guān),不能間接相關(guān)。
② 案例解釋:還是以第一范式中的user_info表作為案例,如果要存儲(chǔ)每個(gè)用戶的省份和省會(huì)城市,我們可能會(huì)設(shè)計(jì)出下面這樣一張表:
用戶編號(hào)(user_id) | 姓名(username) | 年齡(age) | 座機(jī)電話(fixed_phone) | 手機(jī)電話(cell_phone) | 省份(province) | 省會(huì)城市(city) |
---|---|---|---|---|---|---|
1 | 小明 | 20 | 10086 | 18010002000 | 北京市 | 北京市 |
2 | 小紅 | 21 | 10087 | 18010002001 | 黑龍江省 | 哈爾濱市 |
3 | 小王 | 22 | 10088 | 18010002002 | 貴州省 | 貴陽(yáng)市 |
我們將用戶編號(hào)(user_id)作為主鍵,則姓名、年齡、座機(jī)電話、手機(jī)電話都和"用戶"這個(gè)主體強(qiáng)相關(guān),和主鍵直接相關(guān),而省份和省會(huì)城市則和"用戶"這個(gè)主體是弱相關(guān),和主鍵間接相關(guān),并且存在依賴關(guān)系:用戶編號(hào) -> 姓名,姓名 -> 省份,省份 -> 省會(huì)城市,這樣構(gòu)建了用戶編號(hào) -> 省會(huì)城市的間接傳遞關(guān)系,這種關(guān)系會(huì)導(dǎo)致數(shù)據(jù)冗余,而且在執(zhí)行刪除/修改/增加操作的時(shí)候,會(huì)產(chǎn)生異常情況:刪除所有"貴州省"下的用戶信息(即user_id = 3的記錄),"貴州省"和"貴陽(yáng)市"的信息也被刪除了(顯然不合理,因?yàn)槭》葸@個(gè)定義和省份下的人員記錄并沒(méi)有關(guān)系)。
所以我們需要將user_info表拆分,我們通過(guò)省份構(gòu)建數(shù)據(jù)關(guān)系,優(yōu)化后的用戶(user_info)表如下:
用戶編號(hào)(user_id) | 姓名(username) | 年齡(age) | 座機(jī)電話(fixed_phone) | 手機(jī)電話(cell_phone) | 省份(province) |
---|---|---|---|---|---|
1 | 小明 | 20 | 10086 | 18010002000 | 北京市 |
2 | 小紅 | 21 | 10087 | 18010002001 | 黑龍江省 |
3 | 小王 | 22 | 10088 | 18010002002 | 貴州省 |
獨(dú)立拆分出的省份(province)表如下:
省份(province) | 省會(huì)城市(city) |
---|---|
北京市 | 北京市 |
黑龍江省 | 哈爾濱市 |
貴州省 | 貴陽(yáng)市 |
③ 范式優(yōu)點(diǎn):提高了表的獨(dú)立性,降低數(shù)據(jù)存儲(chǔ)冗余。
3. 小結(jié)
作為開發(fā),在日常設(shè)計(jì)數(shù)據(jù)庫(kù)表的時(shí)候可能不會(huì)特意注意使用數(shù)據(jù)庫(kù)范式,但是細(xì)心關(guān)注大部分企業(yè)項(xiàng)目的表結(jié)構(gòu),就會(huì)發(fā)現(xiàn)大部分表都是遵循數(shù)據(jù)庫(kù)范式設(shè)計(jì)的,第二范式和第三范式可能會(huì)混淆概念,第二范式的核心是關(guān)注非主鍵列是否依賴主鍵或者主鍵的一部分,第三范式的核心是關(guān)注非主鍵列是否依賴主鍵,還是依賴其他的非主鍵列。