(三)MySQL之庫表設計篇:一、二、三、四、五範式、BC範式與反範式詳解!
theme: channing-cyan
引言
本文為掘金社區首發簽約文章,14天內禁止轉載,14天后未獲授權禁止轉載,侵權必究!
MySQL
的庫表設計,在很多時候我們都是率性而為,往往在前期的設計中考慮並不全面,同時對於庫表結構的劃分也並不明確,所以很多時候在開發過程中,代碼敲着敲着會去重構某張表結構,甚至大面積重構多張表結構,這種隨心所欲的設計方式,無疑給開發造成了很大困擾。
但實際上設計DB
庫表結構時,也有一些共同需要遵守的規範,這些規範在數據庫設計中被稱為“範式”,理解並掌握這些設計時的規範,能讓咱們在項目之初,設計的庫表結構更為合理且優雅。數據庫範式中,聲名遠揚的有三大範式,但除此之外也有一些其他設計規範,如:
- ①數據庫三大範式(1NF、2NF、3NF
)
- ③第四範式(4NF
)和第五範式:完美範式(5NF
)
- ②巴斯-科德範式(BCNF
)
- ④反範式設計
不過對於上述的幾種設計範式,大部分小夥伴應該僅瞭解過三範式,對於其他的應該未曾接觸,那在本篇中會重點闡述庫表設計時,會用到的這些範式。
一、數據庫三大範式
範式(Normal Form
)在前面也提到過,它就是指設計數據庫時要遵守的一些原則,而數據庫的三大範式,相信諸位在學習數據庫知識時也定然接觸過。三大範式之間,它們是遞進的關係,也就是後續的範式都基於前一個範式的基礎上推行,就好比下面這句話:
今天我要先炒菜,然後吃飯,最後洗碗。
炒菜、吃飯、洗碗三者也屬於遞進關係,後者都建立在前者之上,其順序不能顛倒,比如先吃飯再炒菜,這必然是行不通的。數據庫的三大範式也一樣,第二範式必須建立在第一範式的基礎之上,如若設計的庫表第一範式都不滿足,那定然是無法滿足第二範式的。
寫在前面的話:其實對於數據庫三範式相關的資料,網上也有很多很多,但大部分資料都涉及了太多的概念,通篇看下來也很難讓人理解,因此下述的三範式則會結合具體的設計實例來讓諸位徹底理解三範式。
1.1、第一範式(1NF)
庫表設計時的第一範式,主要是為了確保原子性的,也就是存儲的數據具備不可再分性,這話咋理解呢?上個案例:
sql
SELECT * FROM `zz_student`;
+----------------------+--------+-------+
| student | course | score |
+----------------------+--------+-------+
| 竹子,男,185cm | 語文 | 95 |
| 竹子,男,185cm | 數學 | 100 |
| 竹子,男,185cm | 英語 | 88 |
| 熊貓,女,170cm | 語文 | 99 |
| 熊貓,女,170cm | 數學 | 90 |
| 熊貓,女,170cm | 英語 | 95 |
+----------------------+--------+-------+
在上述的學生表中,其中有一個student
學生列,這一列存儲的數據則明顯不符合第一範式:原子性的規定,因為這一列的數據還可以再拆分為姓名、性別、身高三項數據,因此為了符合第一範式,應該將表結構更改為:
sql
+--------------+-------------+----------------+--------+-------+
| student_name | student_sex | student_height | course | score |
+--------------+-------------+----------------+--------+-------+
| 竹子 | 男 | 185cm | 語文 | 95 |
| 竹子 | 男 | 185cm | 數學 | 100 |
| 竹子 | 男 | 185cm | 英語 | 88 |
| 熊貓 | 女 | 170cm | 語文 | 99 |
| 熊貓 | 女 | 170cm | 數學 | 90 |
| 熊貓 | 女 | 170cm | 英語 | 95 |
+--------------+-------------+----------------+--------+-------+
將student
這一列數據,分別拆分為姓名、性別、身高三列,然後分別存儲對應的數據才合理,通過這樣的優化後,此時zz_student
這張表則符合了數據庫設計的第一範式。
那此刻思考一下:如果不去拆分列滿足第一範式,會造成什麼影響呢?
- 客户端語言和表之間無法很好的生成映射關係。
- 查詢到數據後,需要處理數據時,還需要對
student
字段進行額外拆分。 - 插入數據時,對於第一個字段的值還需要先拼裝後才能進行寫入。
簡單來説,如果按照原本那張形式去做業務開發,顯然操作起來會更加麻煩且複雜一些,但第一範式的原子性,除開對列級別生效之外,行級別的數據也是同理,也就是每一行數據之間是互不影響的,都是獨立的一個整體。
1.2、第二範式(2NF)
上述的第一範式還是比較容易理解,緊接着來看看第二範式,第二範式的要求表中的所有列,其數據都必須依賴於主鍵,也就是一張表只存儲同一類型的數據,不能有任何一列數據與主鍵沒有關係,還是上面的那張表數據為例:
sql
+--------------+-------------+----------------+--------+-------+
| student_name | student_sex | student_height | course | score |
+--------------+-------------+----------------+--------+-------+
| 竹子 | 男 | 185cm | 語文 | 95 |
| 竹子 | 男 | 185cm | 數學 | 100 |
| 竹子 | 男 | 185cm | 英語 | 88 |
| 熊貓 | 女 | 170cm | 語文 | 99 |
| 熊貓 | 女 | 170cm | 數學 | 90 |
| 熊貓 | 女 | 170cm | 英語 | 95 |
+--------------+-------------+----------------+--------+-------+
雖然此時已經滿足了數據庫的第一範式,但此刻觀察course
課程、score
分數這兩列數據,跟前面的幾列數據實際上依賴關係並不大,同時也由於這樣的結構,導致前面幾列的數據出現了大量宂餘,所以此時可以再次拆分一下表結構:
``sql
SELECT * FROM
zz_student`;
+------------+--------+------+--------+--------------+--------------+
| student_id | name | sex | height | department | dean |
+------------+--------+------+--------+--------------+--------------+
| 1 | 竹子 | 男 | 185cm | 計算機系 | 竹子老大 |
| 2 | 熊貓 | 女 | 170cm | 金融系 | 熊貓老大 |
+------------+--------+------+--------+--------------+--------------+
SELECT * FROM zz_course
;
+-----------+-------------+
| course_id | course_name |
+-----------+-------------+
| 1 | 語文 |
| 2 | 數學 |
| 3 | 英語 |
+-----------+-------------+
SELECT * FROM zz_score
;
+----------+------------+-----------+-------+
| score_id | student_id | course_id | score |
+----------+------------+-----------+-------+
| 1 | 1 | 1 | 95 |
| 2 | 1 | 2 | 100 |
| 3 | 1 | 3 | 88 |
| 4 | 2 | 1 | 99 |
| 5 | 2 | 2 | 90 |
| 6 | 2 | 3 | 95 |
+----------+------------+-----------+-------+
``
經過上述結構優化後,之前的一張表此時被我們拆分成學生表、課程表、成績表三張,每張表中的
id字段作為主鍵,其他字段都依賴這個主鍵。無論在那張表中,都可以通過
id`主鍵確定其他字段的信息。
主鍵可以不用
id
,但最好是自增的主鍵ID
,這跟索引有關,後續索引篇詳細講解。
此時再將目光看到先後兩張學生表,原本的學生表有六條學生記錄,其中有四條是宂餘數據,此時的學生表則只有兩條數據,同時這張學生表中只存儲學生信息相關的數據。經過本次結構優化後,每張表的業務屬性都具備“唯一性”,也就是每張表都只會描述了“一件事情”,不會存在一張表中會出現兩個業務屬性(例如之前的學生表包含了學生信息和課程成績)。
1.3、第三範式(3NF)
前面已經對第一範式、第二範式進行了直觀闡述,接下來聊一聊數據庫的第三範式,第三範式要求表中每一列數據不能與主鍵之外的字段有直接關係,怎麼理解呢?基於上述的例子:
sql
+------------+--------+------+--------+--------------+--------------+
| student_id | name | sex | height | department | dean |
+------------+--------+------+--------+--------------+--------------+
| 1 | 竹子 | 男 | 185cm | 計算機系 | 竹子老大 |
| 2 | 熊貓 | 女 | 170cm | 金融系 | 熊貓老大 |
+------------+--------+------+--------+--------------+--------------+
比如這張學生表,目前即符合第一範式,也符合第二範式,但看最後的兩個字段,department
表示當前學生所屬的院校,dean
則表示這個院系的院長是誰。一般來説,一個學生的院長是誰,首先是取決於學生所在的院系的,因此最後的dean
字段明顯與department
字段存在依賴關係,因此需要進一步調整表結構:
``sql
SELECT * FROM
department`;
+---------------+-----------------+-----------------+
| department_id | department_name | department_dean |
+---------------+-----------------+-----------------+
| 1 | 計算機系 | 竹子老大 |
| 2 | 金融系 | 熊貓老大 |
+---------------+-----------------+-----------------+
SELECT * FROM zz_student
;
+------------+--------+------+--------+---------------+
| student_id | name | sex | height | department_id |
+------------+--------+------+--------+---------------+
| 1 | 竹子 | 男 | 185cm | 1 |
| 2 | 熊貓 | 女 | 170cm | 2 |
+------------+--------+------+--------+---------------+
``
經過進一步的結構優化後,又將原本的學生表拆為了院系表、學生表兩張,學生表中則是隻存儲一個院系
ID`,由院系表存儲院系相關的所有數據。至此,學生表中的每個非主鍵字段與其他非主鍵字段之間,都是相互獨立的,之間不會再存在任何依賴性,所有的字段都依賴於主鍵。
那這裏為什麼要調整呢?不調整不行嗎?還真不行,來簡單思考一下不調整結構的情況下會發生什麼問題: - ①當一個院系的院長換人後,需要同時修改學生表中的多條數據。 - ②當一個院長離職後,需要刪除該院長的記錄,會同時刪除多條學生信息。 - ......
也就是如果設計的表結構,無法滿足第三範式,在操作表時就會出現異常,使得整個表較難維護。
1.4、數據庫三範式小結
到這裏就已經將庫表設計的三範式做了直觀闡述,總結如下: - 第一範式:確保原子性,表中每一個列數據都必須是不可再分的字段。 - 第二範式:確保唯一性,每張表都只描述一種業務屬性,一張表只描述一件事。 - 第三範式:確保獨立性,表中除主鍵外,每個字段之間不存在任何依賴,都是獨立的。
經過三範式的示例後,數據庫中的表數量也逐漸多了起來,似乎設計符合三範式的庫表結構,反而更加麻煩了對嗎?答案並非如此,因為在沒有按照範式設計時,會存在幾個問題: - ①整張表數據比較宂餘,同一個學生信息會出現多條。 - ②表結構特別臃腫,不易於操作,要新增一個學生信息時,需添加大量數據。 - ③需要更新其他業務屬性的數據時,比如院系院長換人了,需要修改所有學生的記錄。
但按照三範式將表結構拆開後,假設要新增一條學生數據,就只需要插入學生相關的信息即可,同時如果某個院系的院長換人了,只需要修改院系表中的院長就行,學生表中的數據無需發生任何更改。
因此,經過三範式的設計優化後,整個庫中的所有表結構,會顯得更為優雅,靈活性也會更強。
二、巴斯-科德範式與第四、五範式
第一階段中,簡單瞭解了庫表設計時最基本的三大範式,但除此之外還有另外三種設計範式,即巴斯-科德範式與第四、第五範式,這後續三種範式可能有很多小夥伴沒接觸過,但當你嘗試從網上去了解時,相信絕大部分能看到的資料你都看不懂,例如:
觀察上圖中的描述,這一眼望過去幾乎不是給人看的(沒有詆譭的意思,單純感慨~),其中涉及的碼、完全函數依賴等名詞,至少剛接觸的小白是讀不懂的,因此接下來則依舊採用上面那種案例+大白話的模式,簡單闡述一下這三種設計範式。
2.1、巴斯-科德範式(BCNF)
在瞭解後續這些範式之前,首先得弄明白一個概念,一般在一張表中,可以用於區分每行數據的一個列,通常會被咱們設為主鍵,例如常用的ID
字段就是如此,這類主鍵通常被稱為單一主鍵,即一個列組成的主鍵。但除此之外,還有一個聯合主鍵的概念,也就是由多個列組成的主鍵,相信這點大家在學習數據庫的時候也接觸過。
巴斯-科德範式也被稱為3.5NF
,至於為何不稱為第四範式,這主要是由於它是第三範式的補充版,第三範式的要求是:任何非主鍵字段不能與其他非主鍵字段間存在依賴關係,也就是要求每個非主鍵字段之間要具備獨立性。而巴斯-科德範式在第三範式的基礎上,進一步要求:任何主屬性不能對其他主鍵子集存在依賴。
對於上述的範式定義大家估計有些暈,那用大白話説簡單一點,也就是規定了聯合主鍵中的某列值,不能與聯合主鍵中的其他列存在依賴關係,相信這樣講大家更加容易理解。當然,還是結合一個案例闡述。
先來看一張表:
sql
+-------------------+---------------+--------+------+--------+
| classes | class_adviser | name | sex | height |
+-------------------+---------------+--------+------+--------+
| 計算機-2201班 | 熊竹老師 | 竹子 | 男 | 185cm |
| 金融-2201班 | 竹熊老師 | 熊貓 | 女 | 170cm |
| 計算機-2201班 | 熊竹老師 | 子竹 | 男 | 180cm |
+-------------------+---------------+--------+------+--------+
例如這張學生表,此時假設以classes
班級字段、class_adviser
班主任字段、name
學生姓名字段,組合成一個聯合主鍵,在這裏我們可以通過聯合主鍵,確定學生表中任何一個學生的信息,比如:
熊竹老師管的計算機-2201班,哪個竹子同學有多高啊?
對於這個問題,可以通過上述的聯合主鍵精準定位到表中第一條數據,並且最終能夠給出答案為185cm
。
當然,在這裏有小夥伴有疑惑,為什麼這三個字段可以組成聯合主鍵,和其他字段,例如身高、性別就不行呢?因為主鍵一般都是用於區分不同行數據的,必須要確保唯一性,假設以「班級、班主任、性別」三個字段作為聯合主鍵,此時能通過這個聯合主鍵精準定位到每一條數據嗎?答案是NO
,上個例子理解:
熊竹老師管的計算機-2201班,哪個男同學有多高啊?
現在以這個聯合主鍵還能精準定位到每一條數據嗎?不行的,因為表中有兩位男同學,所以會掃描到多條數據,最終會得到185cm、180cm
兩個答案,顯然「班級、班主任、性別」這三個字段具備重複性,不適合作為主鍵。
到這裏,咱們分析一下,假設以「班級、班主任、學生姓名」三個字段組成聯合主鍵,當前這張表是否符合前面的三大範式呢? - 第一範式:表中每列數據都不可再分,具備原子性,滿足。 - 第二範式:表中每行數據都僅描述了學生信息這一種業務屬性,具備唯一性,滿足。 - 第三範式:除主鍵外,表中非主鍵字段之間都不存在依賴關係,具備獨立性,滿足。
經過上述分析後,當前這張表也符合前面聊到的三大範式,但沒有問題了嗎?有的,在這張表中,一條學生信息中的班主任,取決於學生所在的班級,比如「竹子同學、子竹同學」在「計算機-2201班」,所以它們的班主任都是「熊竹老師」,因此班主任字段其實也依賴於班級字段。那會造成什麼問題呢? - ①當一個班級的班主任老師換人後,需要同時修改學生表中的多條數據。 - ②當一個班主任老師離職後,需要刪除該老師的記錄,會同時刪除多條學生信息。 - ③想要增加一個班級時,同時必須添加學生姓名數據,因為主鍵不允許為空。
通過上述分析可以明顯得知,如果聯合主鍵中的一個字段依賴於另一個字段,同樣也會造成不小的問題,使得整張表的維護性變差,因此這裏需要進一步調整結構:
``sql
SELECT * FROM
zz_classes`;
+------------+-------------------+---------------+
| classes_id | classes_name | class_adviser |
+------------+-------------------+---------------+
| 1 | 計算機-2201班 | 熊竹老師 |
| 2 | 金融-2201班 | 竹熊老師 |
+------------+-------------------+---------------+
SELECT * FROM zz_student
;
+------------+--------+------+--------+
| classes_id | name | sex | height |
+------------+--------+------+--------+
| 1 | 竹子 | 男 | 185cm |
| 2 | 熊貓 | 女 | 170cm |
| 1 | 子竹 | 男 | 180cm |
+------------+--------+------+--------+
``
經過結構調整後,原本的學生表則又被拆為了班級表、學生表兩張,在學生表中只存儲班級
ID,然後使用
classes_id班級
ID和
name`學生姓名兩個字段作為聯合主鍵。
實際情況中,學生表應該有學生
ID
字段作為主鍵,因為同一個班級中也有可能會出現重名的現象,但這裏是為了舉例説明,不要糾結細節~
此時經過調整後,目前的學生表也滿足了巴斯-科德範式,同時對於前面列出的三個問題,調整結構後也不復存在,比如換班主任後只需要更改班級表,無需修改學生表中的學生信息;增加班級時,只需要在班級表中新增數據,也不會影響學生表。
在這裏更專業的做法,應該是對於班級表中的班主任老師信息,再進一步抽象出一張教師表。畢竟班主任字段還依舊與班級字段存在依賴關係,但班級表中的主鍵卻是班級
ID
,所以非主鍵字段之間存在關聯,是不滿足第三範式的(但這裏大家清楚就好啦,我就不做了!)。
OK,經過上述一個案例的剖析後,大家對巴斯-科德範式也有了全面的認知,至於它為何被叫做3.5
範式,相信大家也能夠想清楚答案,因為巴斯-科德範式並沒有定義新的設計規範,僅是對第三範式的做了補充及完善,修正了第三範式。
第三範式只要求非主鍵字段之間,不能存在依賴關係,但沒要求聯合主鍵中的字段不能存在依賴,因此第三範式並未考慮完善,巴斯-科德範式修正的就是這點。
2.2、第四範式(4NF)
認識了巴斯-科德範式後,再來看看數據庫的第四範式,第四範式是基於BC
範式之上的,但在理解第四範式之前,首先得理解“多值依賴”的概念,先貼一下學術論文中常見的定義:
論文來源於:《道客巴巴-多值依賴》,大部分網上資料的描述也都來自於這些學術論文。
能看明白嘛?看不明白就對了,對於這種概念看起來確實令人頭大,沒有相關的技術知識儲備,就算撓破頭皮也看不懂這段描述,因此簡單説一下什麼叫做多值依賴:
一個表中至少需要有三個獨立的字段才會出現多值依賴問題,多值依賴是指表中的字段之間存在一對多的關係,也就是一個字段的具體值會由多個字段來決定。
這樣寫出來似乎比前面好理解一些了,但相對來説還是很繞,那就再上個例子:
sql
SELECT * FROM `zz_user_role_permission`;
+-----------+----------+-------+------------+
| user_name | user_sex | role | permission |
+-----------+----------+-------+------------+
| 竹子 | 男 | ROOT | * |
| 熊貓 | 女 | ADMIN | BACKSTAGE |
| 竹子 | 男 | ADMIN | BACKSTAGE |
| 熊貓 | 女 | USER | LOGIN |
| 竹子 | 男 | USER | LOGIN |
| 子竹 | 男 | USER | LOGIN |
+-----------+----------+-------+------------+
上述是一個經典的業務,也就是一張用户角色權限表,先簡單介紹一下表中各字段的信息:
- user_name
字段 -- 用户名
- role
字段 -- 角色信息:
- USER
:普通用户角色。
- ADMIN
:管理員角色。
- ROOT
:超級管理員角色。
- permission
字段 -- 權限信息:
- *
:超級管理員擁有的權限級別,*
表示所有。
- BACKSTAGE
:管理員擁有的權限級別,表示可以操作後台。
- LOGIN
:普通用户擁有的權限級別,表示可以登錄訪問平台。
理解各字段的值後,假設以「用户名、角色、權限」三個字段作為聯合主鍵,先來分析一下這張表是否滿足之前的範式:
- 表中每列數據都不可再分,具備原子性,滿足第一範式。
- 表中數據都僅描述了用户權限這一種業務屬性,具備唯一性,滿足第二範式。
- 除主鍵外,表中其他字段不存在依賴關係,具備獨立性,滿足第三範式。
- 聯合主鍵中的用户、角色、權限都為獨立字段,不存在依賴性,滿足BC
範式。
因為表中除開聯合主鍵外,就剩下了一個性別字段,因此非主鍵字段必然是獨立的,所以滿足第三範式,但對於BC
範式僅是勉強滿足,因為「用户、角色、權限」之間存在一些依賴關係,不過這裏先不管,畢竟是舉例説明,因此假設是滿足BC
範式。
上述的表結構介紹清楚後,現在來聊聊什麼叫做多值依賴。
此時假設我們需要新增一條數據,那表中的權限字段究竟填什麼?這個值是需要依賴多個字段決定的,權限來自於角色,而角色則來自於用户。也就是説,一個用户可以擁有多個角色,同時一個角色可以擁有多個權限,所以此時咱們無法單獨根據用户名去確定權限值,權限值必須依賴用户、角色兩個字段來決定,這種一個字段的值取決於多個字段才能確定的情況,就被稱為多值依賴。
到這裏是不是就理解了多值依賴?再舉個例子,也就是網上經典的例子。
sql
SELECT * FROM `zz_course_scheduling`;
+--------+------------+--------------+---------------------------+
| course | classes | teacher | book |
+--------+------------+--------------+---------------------------+
| 語文 | 計算機一班 | 竹熊老師 | 人教版-新課標教材 |
| 語文 | 計算機二班 | 黑竹老師 | 人教版-現行教材 |
| 語文 | 計算機三班 | 竹熊老師 | 北師大版教材 |
| 數學 | 計算機一班 | 熊竹老師 | 人教版-新課標教材 |
| 英語 | 計算機一班 | 黑熊老師 | 人教版-新課標教材 |
+--------+------------+--------------+---------------------------+
上述是一張教師排課表,分別有課程、班級、老師、教材四個字段,一個課程會有多位老師授課,同時一個課程也會有多個版本的教材,此時就無法只根據課程、班級、老師任一字段決定教材字段的值,而是要結合班級、課程、老師三個字段,才能確定教材字段的值,比如計算機一班的語文課程,竹熊老師來上,用的是人教版-新課標教材,因此目前教材字段也存在多值依賴的問題,依賴於班級、課程、老師三個字段。
再經過一個案例的薰陶後,是不是對多值依賴的概念理解更深刻啦~
到這裏為止,多值依賴的概念就講清楚了,也正是由於多值依賴的情況出現,又會導致表中出現時數據宂餘、新增、刪除異常等問題出現。
因此第四範式的定義就是要消除表中的多值依賴關係。怎麼做呢?拿前面的權限表舉例。
``sql
SELECT * FROM
zz_users`;
+---------+-----------+----------+----------+---------------------+
| user_id | user_name | user_sex | password | register_time |
+---------+-----------+----------+----------+---------------------+
| 1 | 熊貓 | 女 | 6666 | 2022-08-14 15:22:01 |
| 2 | 竹子 | 男 | 1234 | 2022-09-14 16:17:44 |
| 3 | 子竹 | 男 | 4321 | 2022-09-16 07:42:21 |
+---------+-----------+----------+----------+---------------------+
SELECT * FROM zz_roles
;
+---------+-----------+---------------------+
| role_id | role_name | created_time |
+---------+-----------+---------------------+
| 1 | ROOT | 2022-08-14 15:12:00 |
| 2 | ADMIN | 2022-08-14 15:12:00 |
| 3 | USER | 2022-08-14 15:12:00 |
+---------+-----------+---------------------+
SELECT * FROM zz_permissions
;
+---------------+-----------------+---------------------+
| permission_id | permission_name | created_time |
+---------------+-----------------+---------------------+
| 1 | * | 2022-08-14 15:12:00 |
| 2 | BACKSTAGE | 2022-08-14 15:12:00 |
| 3 | LOGIN | 2022-08-14 15:12:00 |
+---------------+-----------------+---------------------+
SELECT * FROM zz_users_roles
;
+----+---------+---------+
| id | user_id | role_id |
+----+---------+---------+
| 1 | 1 | 1 |
| 2 | 1 | 2 |
| 3 | 1 | 3 |
| 4 | 2 | 2 |
| 5 | 2 | 3 |
| 6 | 3 | 3 |
+----+---------+---------+
SELECT * FROM zz_roles_permissions
;
+----+---------+---------------+
| id | role_id | permission_id |
+----+---------+---------------+
| 1 | 1 | 1 |
| 2 | 2 | 2 |
| 3 | 3 | 3 |
+----+---------+---------------+
``
觀察上述的五張表,如果有做過權限設計,或用過
Shiro`框架的小夥伴應該會感到額外的親切,這個正是大名鼎鼎的權限五表,將原本的用户角色權限表,拆分成了用户表、角色表、權限表、用户角色關係表、角色權限關係表。經過這次拆分之後,一方面用户表、角色表、權限表中都不會有數據宂餘,第二方面無論是要刪除亦或新增一個角色、權限時,都不會影響其他表。
後面的兩張關係表,主要是為了維護用户、角色、權限三者之間的關係。
對於前面的教師排課表,就不再拆分啦,大家如若想要鍛鍊一下掌握程度,可自行將其拆分成符合第四範式的表結構。
2.3、第五範式(5NF)/完美範式
瞭解了第四範式後,再來看看第五範式,這個範式也被稱為完美範式,先來説一下第五範式的定義:建立在4NF
的基礎上,進一步消除表中的連接依賴,直到表中的連接依賴都是主鍵所藴含的。等等,連接依賴又是個啥?
看不懂對不?説實話我也看着迷糊,大概能確定的是:多值依賴也屬於連接依賴的一種,而連接依賴也包含了多值依賴,大家可以參考一下《這個》。
第五範式解決的是無損連接問題,但對於第五範式我自個兒也沒理解透徹,因此不再講解第五範式了,防止誤導諸位,同時如若有對這塊十分了解的大佬,可以留言指點一下。
2.4、六大範式小結
經過一系列的闡述後,其實不難發現,越到後面的範式,越難令人理解,同時為了讓表滿足更高級別的範式,越往後付出代價也越大,而且拆分出的表數量也會越多,所以一般實際開發中,對於庫表的設計最高滿足BC
範式即可,再往後就沒意義了,因為表數量一多,查詢也好,寫入也罷,性能會越來越差。
同時,由於後面的幾種範式在實際項目中應用較少,因此關於這塊的資料也會較少,後續的幾種範式也僅有一些學術機構在琢磨,所以當我們試圖去窺探時,能看到的也是一堆學術詞彙。
除開聊到的六大範式外,還有一種範式名為域鍵範式,也被稱之為終極範式,但目前也僅有學術機構在研究,在生產環境中實際的用途也不大,諸位有興趣可以自己看看,最後再上一個各範式之間的遞進關係圖:
- 第一範式:原子性,每個字段的值不能再分。
- 第二範式:唯一性,表內每行數據必須描述同一業務屬性的數據。
- 第三範式:獨立性,表中每個非主鍵字段之間不能存在依賴性。
- 巴斯範式:主鍵字段獨立性,聯合主鍵字段之間不能存在依賴性。
- 第四範式:表中字段不能存在多值依賴關係。
- 第五範式:表中字段的數據之間不能存在連接依賴關係。
- 域鍵範式:試圖研究出一個庫表設計時的終極完美範式。
三、數據庫反範式設計
遵循數據庫範式設計的結構優點很明顯,它避免了大量的數據宂餘,節省了大量存儲空間,同時讓整體結構更為優雅,能讓SQL
操作更加便捷且減少出錯。但隨着範式的級別越高,設計出的結構會更加精細化,原本一張表的數據會被分攤到多張表中存儲,表的數量隨之越來越多。
但隨之而來的不僅僅只有好處,也存在一個致命問題,也就是當同時需要這些數據時,只能採用聯表查詢的形式檢索數據,有時候甚至為了一個字段的數據,也需要做一次連表查詢才能獲得。這其中的開銷無疑是花費巨大的,尤其是當連接的表不僅兩三張而是很多張時,有可能還會造成索引失效,這種情況帶來的資源、時間開銷簡直是一個噩夢,這會嚴重地影響整個業務系統的性能。
因此,也正是由於上述一些問題,在設計庫表結構時,我們不一定要
100%
遵守範式準則。這種違反數據庫範式的設計方法,就被稱之為 反範式設計。
遵循範式設計也好,反範式設計也罷,本身兩者之間並沒有優劣之分,只要能夠對業務更有利,那就可以稱之為好的設計方案。範式的目的僅在於讓我們設計的結構更優雅合理,有時候在表中多增加一個字段,從數據庫的角度來看,數據會存在宂餘問題,會讓表結構違反範式的定義,但如若能夠在實際情況中減少大量的連表查詢,這種設計自然也是可取的。
也就是説,在設計時千萬不要拘泥於規則之內,一定要結合實際業務考慮,遵循業務優先的原則去設計結構。
當然,對於反範式設計也無需再用更多的語言去描述了,因為本質上就是一個概念詞,也就是不遵循數據庫範式設計的結構,就被稱為反範式結構。不過要牢記的一點是:不是所有不遵循數據庫範式的結構設計都被稱為反範式,反範式設計是指自己知道會破壞範式,但對業務帶來好處大於壞處時,刻意設計出破壞範式的結構。
隨意設計出的結構,不滿足範式要求,同時還無法給業務上帶來收益的,這並不被稱為反範式設計,反範式設計是一種刻意為之的思想。
四、庫表設計篇總結
在本篇中詳細闡述了DB
庫表設計時的一些思想,也就是範式與反範式設計理論,這些理論僅僅只是一套方法論,實際開發過程中,還是需要根據業務來設計出最合適的結構。在文中提及了六種範式,但一般項目中僅需滿足到第三範式或BC
範式即可,因為這個度剛剛好,再往後就會因為過於精細化設計,導致整體性能反而下降。控制到第三範式的級別,一方面數據不會有太多宂餘,第二方面也不會對性能影響過大。
同時,如若打破範式的設定能對業務更有利,那也可以違背範式原則去設計。
不過雖説這些屬於方法論,但認真看下來之後,相信諸位在之後設計庫表結構應該會潛意識的遵循一些範式原則,也會盡量的將表結構設計的更為優雅,從而也能讓咱們在開發過程中,減少調整庫表結構的次數和帶來的影響。
一般而言,庫表結構設計的是否合理,區別如下:
- 不合理的結構設計會造成的問題:
- 數據宂餘,會浪費一定程度上的存儲空間
- 不便於常規
SQL
操作(例如插入、刪除),甚至會出現異常
- 合理的結構設計帶來的好處:
- 節省空間,
SQL
執行時能節省內存空間,數據存儲時能節省磁盤空間 - 數據劃分較為合理,
DB
性能整體較高,並且數據也非常完整 - 結構便於維護和進行常規
SQL
操作
- 節省空間,
- (七)MySQL事務篇:ACID原則、事務隔離級別及事務機制原理剖析
- 深入理解SpringMVC工作原理,像大牛一樣手寫SpringMVC框架
- (三)MySQL之庫表設計篇:一、二、三、四、五範式、BC範式與反範式詳解!
- (五)MySQL索引應用篇:建立索引的正確姿勢與使用索引的最佳指南!
- (六)MySQL索引原理篇:深入數據庫底層揭開索引機制的神祕面紗!
- (五)網絡編程之流量接入層設計:基於性能怪獸從零構建日均億級吞吐量的網關架構!
- (四)網絡編程之請求分發篇:負載均衡靜態調度算法、平滑輪詢加權、一致性哈希、最小活躍數算法實踐!
- (三)Nginx一網打盡:動靜分離、壓縮、緩存、黑白名單、跨域、高可用、性能優化...想要的這都有!
- Redis綜述篇:與面試官徹夜長談Redis緩存、持久化、淘汰機制、哨兵、集羣底層原理!
- (九)JVM成神路之性能調優、GC調試、各內存區、Linux參數大全及實用小技巧
- (八)JVM成神路之GC分區篇:G1、ZGC、ShenandoahGC高性能收集器深入剖析