字節(jié)二面:為了性能,你會違反數(shù)據(jù)庫三范式嗎?
三范式是數(shù)據(jù)庫設(shè)計中最基本的三個規(guī)范,那么,三范式到底是什么?在實際開發(fā)中,為了性能,我們需要違反三范式嗎?這篇文章,我們一起來聊一聊。
一、三大范式
1. 第一范式(1NF)
第一范式要求數(shù)據(jù)庫中的每個表格的每個字段(列)都具有原子性,即字段中的值不可再分割。換句話說,每個字段只能存儲一個單一的值,不能包含集合、數(shù)組或重復(fù)的組,即確保每列保持原子性。
如下示例: 假設(shè)有一個學(xué)生表 Student,結(jié)構(gòu)如下:
學(xué)生ID | 姓名 | 電話號碼 |
1 | 張三 | 123456789, 987654321 |
2 | 李四 | 555555555 |
在這個表中,電話號碼字段包含多個號碼,違反了1NF的原子性要求。為了滿足1NF,需要將電話號碼拆分為單獨的記錄或創(chuàng)建一個新的表。
滿足 1NF后的設(shè)計:
學(xué)生表 Student:
學(xué)生ID | 姓名 |
1 | 張三 |
2 | 李四 |
電話表 Phone:
電話ID | 學(xué)生ID | 電話號碼 |
1 | 1 | 123456789 |
2 | 1 | 987654321 |
3 | 2 | 555555555 |
2. 第二范式(2NF)
第二范式要求滿足第一范式,并且消除表中的部分依賴,即非主鍵字段必須完全依賴于主鍵,而不是僅依賴于主鍵的一部分,即確保表中的每列都和主鍵相關(guān)。這主要適用于復(fù)合主鍵的情況。
如下示例:假設(shè)有一個訂單詳情表 OrderDetail,結(jié)構(gòu)如下:
訂單ID | 商品ID | 商品名稱 | 數(shù)量 | 單價 |
1001 | A01 | 蘋果 | 10 | 2.5 |
1001 | A02 | 橙子 | 5 | 3.0 |
1002 | A01 | 蘋果 | 7 | 2.5 |
在上述表中,主鍵是復(fù)合主鍵 (訂單ID, 商品ID)。商品名稱和單價只依賴于復(fù)合主鍵中的商品ID,而不是整個主鍵,存在部分依賴,違反了2NF。
滿足 2NF后的設(shè)計:
訂單詳情表 OrderDetail:
訂單ID | 商品ID | 數(shù)量 |
1001 | A01 | 10 |
1001 | A02 | 5 |
1002 | A01 | 7 |
商品表 Product:
商品ID | 商品名稱 | 單價 |
A01 | 蘋果 | 2.5 |
A02 | 橙子 | 3.0 |
3. 第三范式(3NF)
第三范式要求滿足第二范式,并且消除表中的傳遞依賴,即非主鍵字段不應(yīng)依賴于其他非主鍵字段。換句話說,所有非主鍵字段必須直接依賴于主鍵,而不是通過其他非主鍵字段間接依賴,或者說:確保每列都和主鍵列直接相關(guān),而不是間接相關(guān)。
如下示例:假設(shè)有一張員工表 Employee,結(jié)構(gòu)如下:
員工ID | 員工姓名 | 部門ID | 部門名稱 |
E01 | 王五 | D01 | 銷售部 |
E02 | 趙六 | D02 | 技術(shù)部 |
E03 | 孫七 | D01 | 銷售部 |
在這張表中,部門名稱依賴于部門ID,而部門ID依賴于主鍵員工ID,形成了傳遞依賴,違反了3NF。
滿足3NF后的設(shè)計:
員工表 Employee:
員工ID | 員工姓名 | 部門ID |
E01 | 王五 | D01 |
E02 | 趙六 | D02 |
E03 | 孫七 | D01 |
部門表 Department:
部門ID | 部門名稱 |
D01 | 銷售部 |
D02 | 技術(shù)部 |
通過將部門信息移到單獨的表中,消除了傳遞依賴,使得數(shù)據(jù)庫結(jié)構(gòu)符合第三范式。
最后,我們總結(jié)一下數(shù)據(jù)庫設(shè)計的三大范式:
- 第一范式(1NF): 確保每個字段的值都是原子性的,不可再分。
- 第二范式(2NF): 在滿足 1NF的基礎(chǔ)上,消除部分依賴,確保非主鍵字段完全依賴于主鍵。
- 第三范式(3NF): 在滿足 2NF的基礎(chǔ)上,消除傳遞依賴,確保非主鍵字段直接依賴于主鍵。
二、破壞三范式
在實際工作中,盡管遵循數(shù)據(jù)庫的三大范式(1NF、2NF、3NF)有助于提高數(shù)據(jù)的一致性和減少冗余,但在某些情況下,為了滿足性能、簡化設(shè)計或特定業(yè)務(wù)需求,我們可能需要違反這些范式。
下面列舉了一些常見的破壞三范式的原因及對應(yīng)的示例。
1. 性能優(yōu)化
在高并發(fā)、大數(shù)據(jù)量的應(yīng)用場景中,嚴(yán)格遵循三范式可能導(dǎo)致頻繁的聯(lián)表查詢,增加查詢時間和系統(tǒng)負(fù)載。為了提高查詢性能,設(shè)計者可能會通過冗余數(shù)據(jù)來減少聯(lián)表操作。
假設(shè)有一個電商系統(tǒng),包含訂單表 Orders 和用戶表 Users。在嚴(yán)格 3NF設(shè)計中,訂單表只存儲 用戶ID,需要通過聯(lián)表查詢獲取用戶的詳細(xì)信息。
但是,為了查詢性能,我們通常會在訂單表中冗余存儲 用戶姓名 和 用戶地址等信息,因此,查詢訂單信息時無需聯(lián)表查詢 Users 表,從而提升查詢速度。
破壞 3NF后的設(shè)計:
訂單ID | 用戶ID | 用戶姓名 | 用戶地址 | 訂單日期 | 總金額 |
1001 | U01 | 張三 | 北京市 | 2023-10-01 | 500元 |
1002 | U02 | 李四 | 上海市 | 2023-10-02 | 300元 |
2. 簡化查詢和開發(fā)
嚴(yán)格規(guī)范化可能導(dǎo)致數(shù)據(jù)庫結(jié)構(gòu)過于復(fù)雜,增加開發(fā)和維護的難度,為了簡化查詢邏輯和減少開發(fā)復(fù)雜度,我們也可能會選擇適當(dāng)?shù)娜哂唷?/p>
比如,在內(nèi)容管理系統(tǒng)(CMS)中,文章表 Articles 和分類表 Categories 通常是獨立的,如果頻繁需要顯示文章所屬的分類名稱,聯(lián)表查詢可能增加復(fù)雜性。因此,通過在 Articles 表中直接存儲 分類名稱,可以簡化前端展示邏輯,減少開發(fā)工作量。
破壞 3NF后的設(shè)計:
文章ID | 標(biāo)題 | 內(nèi)容 | 分類ID | 分類名稱 |
A01 | 文章一 | … | C01 | 技術(shù) |
A02 | 文章二 | … | C02 | 生活 |
3. 報表和數(shù)據(jù)倉庫
在數(shù)據(jù)倉庫和報表系統(tǒng)中,通常需要快速讀取和聚合大量數(shù)據(jù)。為了優(yōu)化查詢性能和數(shù)據(jù)分析,可能會采用冗余的數(shù)據(jù)結(jié)構(gòu),甚至使用星型或雪花型模式,這些模式并不完全符合三范式。
在銷售數(shù)據(jù)倉庫中,為了快速生成銷售報表,可能會創(chuàng)建一個包含維度信息的事實表。
破壞 3NF后的設(shè)計:
銷售ID | 產(chǎn)品ID | 產(chǎn)品名稱 | 類別 | 銷售數(shù)量 | 銷售金額 | 銷售日期 |
S01 | P01 | 手機 | 電子 | 100 | 50000元 | 2023-10-01 |
S02 | P02 | 書籍 | 教育 | 200 | 20000元 | 2023-10-02 |
在事實表中直接存儲 產(chǎn)品名稱 和 類別,避免了需要聯(lián)表查詢維度表,提高了報表生成的效率。
4. 特殊業(yè)務(wù)需求
在某些業(yè)務(wù)場景下,可能需要快速響應(yīng)特定的查詢或操作,這時通過適當(dāng)?shù)娜哂嘣O(shè)計可以滿足業(yè)務(wù)需求。
比如,在實時交易系統(tǒng)中,為了快速計算用戶的賬戶余額,可能會在用戶表中直接存儲當(dāng)前余額,而不是每次交易時都計算。
破壞 3NF后的設(shè)計:
用戶ID | 用戶名 | 當(dāng)前余額 |
U01 | 王五 | 10000元 |
U02 | 趙六 | 5000元 |
在交易記錄表中存儲每筆交易的增減,但直接在用戶表中維護 當(dāng)前余額,避免了每次查詢時的復(fù)雜計算。
5. 兼顧讀寫性能
在某些應(yīng)用中,讀操作遠多于寫操作。為了優(yōu)化讀性能,可能會通過數(shù)據(jù)冗余來提升查詢速度,而接受在數(shù)據(jù)寫入時需要額外的維護工作。
社交媒體平臺中,用戶的好友數(shù)常被展示在用戶主頁上。如果每次請求都計算好友數(shù)量,效率低下??梢栽谟脩舯碇芯S護一個 好友數(shù) 字段。
破壞3NF后的設(shè)計:
用戶ID | 用戶名 | 好友數(shù) |
U01 | Alice | 150 |
U02 | Bob | 200 |
通過在 Users 表中冗余存儲 好友數(shù),可以快速展示,無需實時計算。
6. 快速迭代和靈活性
在快速發(fā)展的產(chǎn)品或初創(chuàng)企業(yè)中,數(shù)據(jù)庫設(shè)計可能需要頻繁調(diào)整。過度規(guī)范化可能導(dǎo)致設(shè)計不夠靈活,影響迭代速度。適當(dāng)?shù)娜哂嘣O(shè)計可以提高開發(fā)的靈活性和速度。
一個初創(chuàng)電商平臺在初期快速上線,數(shù)據(jù)庫設(shè)計時為了簡化開發(fā),可能會將用戶的收貨地址直接存儲在訂單表中,而不是單獨創(chuàng)建地址表。
破壞3NF后的設(shè)計:
訂單ID | 用戶ID | 用戶名 | 收貨地址 | 訂單日期 | 總金額 |
O1001 | U01 | 李雷 | 北京市海淀區(qū)… | 2023-10-01 | 800元 |
O1002 | U02 | 韓梅梅 | 上海市浦東新區(qū)… | 2023-10-02 | 1200元 |
這樣設(shè)計可以快速上線,后續(xù)根據(jù)需求再進行規(guī)范化和優(yōu)化。
7. 降低復(fù)雜性和提高可理解性
有時,過度規(guī)范化可能使數(shù)據(jù)庫結(jié)構(gòu)變得復(fù)雜,難以理解和維護。適度的冗余可以降低設(shè)計的復(fù)雜性,提高團隊對數(shù)據(jù)庫結(jié)構(gòu)的理解和溝通效率。
在一個學(xué)校管理系統(tǒng)中,如果將學(xué)生的班級信息獨立為多個表,可能增加理解難度。為了簡化設(shè)計,可以在學(xué)生表中直接存儲班級名稱。
破壞3NF后的設(shè)計:
學(xué)生ID | 姓名 | 班級ID | 班級名稱 | 班主任 |
S01 | 張三 | C01 | 三年級一班 | 李老師 |
S02 | 李四 | C02 | 三年級二班 | 王老師 |
通過在學(xué)生表中直接存儲 班級名稱 和 班主任,減少了表的數(shù)量,簡化了設(shè)計。
三、總結(jié)
本文,我們分析了數(shù)據(jù)庫的三范式以及對應(yīng)的示例,它是數(shù)據(jù)庫設(shè)計的基本規(guī)范。但是,在實際工作中,為了滿足性能、簡化設(shè)計、快速迭代或特定業(yè)務(wù)需求,我們很多時候并不會嚴(yán)格地遵守三范式。
所以說,架構(gòu)很多時候都是業(yè)務(wù)需求、數(shù)據(jù)一致性、系統(tǒng)性能、開發(fā)效率等各種因素權(quán)衡的結(jié)果,我們需要根據(jù)具體應(yīng)用場景做出合理的設(shè)計選擇。