引言
? ?MySQL
的庫表設計,在很多時候我們都是率性而為,往往在前期的設計中考慮并不全面,同時對于庫表結構的劃分也并不明確,所以很多時候在開發過程中,代碼敲著敲著會去重構某張表結構,甚至大面積重構多張表結構,這種隨心所欲的設計方式,無疑給開發造成了很大困擾。
[圖片上傳失敗...(image-9af152-1670572692821)]
? ?但實際上設計DB
庫表結構時,也有一些共同需要遵守的規范,這些規范在數據庫設計中被稱為“范式”,理解并掌握這些設計時的規范,能讓咱們在項目之初,設計的庫表結構更為合理且優雅。數據庫范式中,聲名遠揚的有三大范式,但除此之外也有一些其他設計規范,如:
- ①數據庫三大范式(
1NF、2NF、3NF
) - ③第四范式(
4NF
)和第五范式:完美范式(5NF
) - ②巴斯-科德范式(
BCNF
) - ④反范式設計
不過對于上述的幾種設計范式,大部分小伙伴應該僅了解過三范式,對于其他的應該未曾接觸,那在本篇中會重點闡述庫表設計時,會用到的這些范式。
一、數據庫三大范式
? ?范式(Normal Form
)在前面也提到過,它就是指設計數據庫時要遵守的一些原則,而數據庫的三大范式,相信諸位在學習數據庫知識時也定然接觸過。三大范式之間,它們是遞進的關系,也就是后續的范式都基于前一個范式的基礎上推行,就好比下面這句話:
今天我要先炒菜,然后吃飯,最后洗碗。
炒菜、吃飯、洗碗三者也屬于遞進關系,后者都建立在前者之上,其順序不能顛倒,比如先吃飯再炒菜,這必然是行不通的。數據庫的三大范式也一樣,第二范式必須建立在第一范式的基礎之上,如若設計的庫表第一范式都不滿足,那定然是無法滿足第二范式的。
寫在前面的話:其實對于數據庫三范式相關的資料,網上也有很多很多,但大部分資料都涉及了太多的概念,通篇看下來也很難讓人理解,因此下述的三范式則會結合具體的設計實例來讓諸位徹底理解三范式。
1.1、第一范式(1NF)
? ?庫表設計時的第一范式,主要是為了確保原子性的,也就是存儲的數據具備不可再分性,這話咋理解呢?上個案例:
SELECT * FROM `zz_student`;
+----------------------+--------+-------+
| student | course | score |
+----------------------+--------+-------+
| 竹子,男,185cm | 語文 | 95 |
| 竹子,男,185cm | 數學 | 100 |
| 竹子,男,185cm | 英語 | 88 |
| 熊貓,女,170cm | 語文 | 99 |
| 熊貓,女,170cm | 數學 | 90 |
| 熊貓,女,170cm | 英語 | 95 |
+----------------------+--------+-------+
在上述的學生表中,其中有一個student
學生列,這一列存儲的數據則明顯不符合第一范式:原子性的規定,因為這一列的數據還可以再拆分為姓名、性別、身高三項數據,因此為了符合第一范式,應該將表結構更改為:
+--------------+-------------+----------------+--------+-------+
| 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)
? ?上述的第一范式還是比較容易理解,緊接著來看看第二范式,第二范式的要求表中的所有列,其數據都必須依賴于主鍵,也就是一張表只存儲同一類型的數據,不能有任何一列數據與主鍵沒有關系,還是上面的那張表數據為例:
+--------------+-------------+----------------+--------+-------+
| student_name | student_sex | student_height | course | score |
+--------------+-------------+----------------+--------+-------+
| 竹子 | 男 | 185cm | 語文 | 95 |
| 竹子 | 男 | 185cm | 數學 | 100 |
| 竹子 | 男 | 185cm | 英語 | 88 |
| 熊貓 | 女 | 170cm | 語文 | 99 |
| 熊貓 | 女 | 170cm | 數學 | 90 |
| 熊貓 | 女 | 170cm | 英語 | 95 |
+--------------+-------------+----------------+--------+-------+
雖然此時已經滿足了數據庫的第一范式,但此刻觀察course
課程、score
分數這兩列數據,跟前面的幾列數據實際上依賴關系并不大,同時也由于這樣的結構,導致前面幾列的數據出現了大量冗余,所以此時可以再次拆分一下表結構:
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)
? ?前面已經對第一范式、第二范式進行了直觀闡述,接下來聊一聊數據庫的第三范式,第三范式要求表中每一列數據不能與主鍵之外的字段有直接關系,怎么理解呢?基于上述的例子:
+------------+--------+------+--------+--------------+--------------+
| student_id | name | sex | height | department | dean |
+------------+--------+------+--------+--------------+--------------+
| 1 | 竹子 | 男 | 185cm | 計算機系 | 竹子老大 |
| 2 | 熊貓 | 女 | 170cm | 金融系 | 熊貓老大 |
+------------+--------+------+--------+--------------+--------------+
比如這張學生表,目前即符合第一范式,也符合第二范式,但看最后的兩個字段,department
表示當前學生所屬的院校,dean
則表示這個院系的院長是誰。一般來說,一個學生的院長是誰,首先是取決于學生所在的院系的,因此最后的dean
字段明顯與department
字段存在依賴關系,因此需要進一步調整表結構:
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、數據庫三范式小結
? ?到這里就已經將庫表設計的三范式做了直觀闡述,總結如下:
- 第一范式:確保原子性,表中每一個列數據都必須是不可再分的字段。
- 第二范式:確保唯一性,每張表都只描述一種業務屬性,一張表只描述一件事。
- 第三范式:確保獨立性,表中除主鍵外,每個字段之間不存在任何依賴,都是獨立的。
經過三范式的示例后,數據庫中的表數量也逐漸多了起來,似乎設計符合三范式的庫表結構,反而更加麻煩了對嗎?答案并非如此,因為在沒有按照范式設計時,會存在幾個問題:
- ①整張表數據比較冗余,同一個學生信息會出現多條。
- ②表結構特別臃腫,不易于操作,要新增一個學生信息時,需添加大量數據。
- ③需要更新其他業務屬性的數據時,比如院系院長換人了,需要修改所有學生的記錄。
但按照三范式將表結構拆開后,假設要新增一條學生數據,就只需要插入學生相關的信息即可,同時如果某個院系的院長換人了,只需要修改院系表中的院長就行,學生表中的數據無需發生任何更改。
因此,經過三范式的設計優化后,整個庫中的所有表結構,會顯得更為優雅,靈活性也會更強。
二、巴斯-科德范式與第四、五范式
? ?第一階段中,簡單了解了庫表設計時最基本的三大范式,但除此之外還有另外三種設計范式,即巴斯-科德范式與第四、第五范式,這后續三種范式可能有很多小伙伴沒接觸過,但當你嘗試從網上去了解時,相信絕大部分能看到的資料你都看不懂,例如:
[圖片上傳失敗...(image-437eda-1670572692821)]
觀察上圖中的描述,這一眼望過去幾乎不是給人看的(沒有詆毀的意思,單純感慨~),其中涉及的碼、完全函數依賴等名詞,至少剛接觸的小白是讀不懂的,因此接下來則依舊采用上面那種案例+大白話的模式,簡單闡述一下這三種設計范式。
2.1、巴斯-科德范式(BCNF)
? ?在了解后續這些范式之前,首先得弄明白一個概念,一般在一張表中,可以用于區分每行數據的一個列,通常會被咱們設為主鍵,例如常用的ID
字段就是如此,這類主鍵通常被稱為單一主鍵,即一個列組成的主鍵。但除此之外,還有一個聯合主鍵的概念,也就是由多個列組成的主鍵,相信這點大家在學習數據庫的時候也接觸過。
? ?巴斯-科德范式也被稱為3.5NF
,至于為何不稱為第四范式,這主要是由于它是第三范式的補充版,第三范式的要求是:任何非主鍵字段不能與其他非主鍵字段間存在依賴關系,也就是要求每個非主鍵字段之間要具備獨立性。而巴斯-科德范式在第三范式的基礎上,進一步要求:任何主屬性不能對其他主鍵子集存在依賴。
對于上述的范式定義大家估計有些暈,那用大白話說簡單一點,也就是規定了聯合主鍵中的某列值,不能與聯合主鍵中的其他列存在依賴關系,相信這樣講大家更加容易理解。當然,還是結合一個案例闡述。
先來看一張表:
+-------------------+---------------+--------+------+--------+
| classes | class_adviser | name | sex | height |
+-------------------+---------------+--------+------+--------+
| 計算機-2201班 | 熊竹老師 | 竹子 | 男 | 185cm |
| 金融-2201班 | 竹熊老師 | 熊貓 | 女 | 170cm |
| 計算機-2201班 | 熊竹老師 | 子竹 | 男 | 180cm |
+-------------------+---------------+--------+------+--------+
例如這張學生表,此時假設以classes
班級字段、class_adviser
班主任字段、name
學生姓名字段,組合成一個聯合主鍵,在這里我們可以通過聯合主鍵,確定學生表中任何一個學生的信息,比如:
熊竹老師管的計算機-2201班,哪個竹子同學有多高啊?
對于這個問題,可以通過上述的聯合主鍵精準定位到表中第一條數據,并且最終能夠給出答案為185cm
。
當然,在這里有小伙伴有疑惑,為什么這三個字段可以組成聯合主鍵,和其他字段,例如身高、性別就不行呢?因為主鍵一般都是用于區分不同行數據的,必須要確保唯一性,假設以「班級、班主任、性別」三個字段作為聯合主鍵,此時能通過這個聯合主鍵精準定位到每一條數據嗎?答案是NO
,上個例子理解:
熊竹老師管的計算機-2201班,哪個男同學有多高啊?
現在以這個聯合主鍵還能精準定位到每一條數據嗎?不行的,因為表中有兩位男同學,所以會掃描到多條數據,最終會得到185cm、180cm
兩個答案,顯然「班級、班主任、性別」這三個字段具備重復性,不適合作為主鍵。
到這里,咱們分析一下,假設以「班級、班主任、學生姓名」三個字段組成聯合主鍵,當前這張表是否符合前面的三大范式呢?
- 第一范式:表中每列數據都不可再分,具備原子性,滿足。
- 第二范式:表中每行數據都僅描述了學生信息這一種業務屬性,具備唯一性,滿足。
- 第三范式:除主鍵外,表中非主鍵字段之間都不存在依賴關系,具備獨立性,滿足。
經過上述分析后,當前這張表也符合前面聊到的三大范式,但沒有問題了嗎?有的,在這張表中,一條學生信息中的班主任,取決于學生所在的班級,比如「竹子同學、子竹同學」在「計算機-2201班」,所以它們的班主任都是「熊竹老師」,因此班主任字段其實也依賴于班級字段。那會造成什么問題呢?
- ①當一個班級的班主任老師換人后,需要同時修改學生表中的多條數據。
- ②當一個班主任老師離職后,需要刪除該老師的記錄,會同時刪除多條學生信息。
- ③想要增加一個班級時,同時必須添加學生姓名數據,因為主鍵不允許為空。
通過上述分析可以明顯得知,如果聯合主鍵中的一個字段依賴于另一個字段,同樣也會造成不小的問題,使得整張表的維護性變差,因此這里需要進一步調整結構:
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
范式之上的,但在理解第四范式之前,首先得理解“多值依賴”的概念,先貼一下學術論文中常見的定義:
[圖片上傳失敗...(image-fbeca0-1670572692821)]
論文來源于:《道客巴巴-多值依賴》,大部分網上資料的描述也都來自于這些學術論文。
能看明白嘛?看不明白就對了,對于這種概念看起來確實令人頭大,沒有相關的技術知識儲備,就算撓破頭皮也看不懂這段描述,因此簡單說一下什么叫做多值依賴:
一個表中至少需要有三個獨立的字段才會出現多值依賴問題,多值依賴是指表中的字段之間存在一對多的關系,也就是一個字段的具體值會由多個字段來決定。
這樣寫出來似乎比前面好理解一些了,但相對來說還是很繞,那就再上個例子:
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
范式。
上述的表結構介紹清楚后,現在來聊聊什么叫做多值依賴。
此時假設我們需要新增一條數據,那表中的權限字段究竟填什么?這個值是需要依賴多個字段決定的,權限來自于角色,而角色則來自于用戶。也就是說,一個用戶可以擁有多個角色,同時一個角色可以擁有多個權限,所以此時咱們無法單獨根據用戶名去確定權限值,權限值必須依賴用戶、角色兩個字段來決定,這種一個字段的值取決于多個字段才能確定的情況,就被稱為多值依賴。
到這里是不是就理解了多值依賴?再舉個例子,也就是網上經典的例子。
SELECT * FROM `zz_course_scheduling`;
+--------+------------+--------------+---------------------------+
| course | classes | teacher | book |
+--------+------------+--------------+---------------------------+
| 語文 | 計算機一班 | 竹熊老師 | 人教版-新課標教材 |
| 語文 | 計算機二班 | 黑竹老師 | 人教版-現行教材 |
| 語文 | 計算機三班 | 竹熊老師 | 北師大版教材 |
| 數學 | 計算機一班 | 熊竹老師 | 人教版-新課標教材 |
| 英語 | 計算機一班 | 黑熊老師 | 人教版-新課標教材 |
+--------+------------+--------------+---------------------------+
上述是一張教師排課表,分別有課程、班級、老師、教材四個字段,一個課程會有多位老師授課,同時一個課程也會有多個版本的教材,此時就無法只根據課程、班級、老師任一字段決定教材字段的值,而是要結合班級、課程、老師三個字段,才能確定教材字段的值,比如計算機一班的語文課程,竹熊老師來上,用的是人教版-新課標教材,因此目前教材字段也存在多值依賴的問題,依賴于班級、課程、老師三個字段。
再經過一個案例的熏陶后,是不是對多值依賴的概念理解更深刻啦~
到這里為止,多值依賴的概念就講清楚了,也正是由于多值依賴的情況出現,又會導致表中出現時數據冗余、新增、刪除異常等問題出現。
因此第四范式的定義就是要消除表中的多值依賴關系。怎么做呢?拿前面的權限表舉例。
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
的基礎上,進一步消除表中的連接依賴,直到表中的連接依賴都是主鍵所蘊含的。等等,連接依賴又是個啥?
[圖片上傳失敗...(image-8c2edc-1670572692821)]
看不懂對不?說實話我也看著迷糊,大概能確定的是:多值依賴也屬于連接依賴的一種,而連接依賴也包含了多值依賴,大家可以參考一下《這個》。
第五范式解決的是無損連接問題,但對于第五范式我自個兒也沒理解透徹,因此不再講解第五范式了,防止誤導諸位,同時如若有對這塊十分了解的大佬,可以留言指點一下。
2.4、六大范式小結
? ?經過一系列的闡述后,其實不難發現,越到后面的范式,越難令人理解,同時為了讓表滿足更高級別的范式,越往后付出代價也越大,而且拆分出的表數量也會越多,所以一般實際開發中,對于庫表的設計最高滿足BC
范式即可,再往后就沒意義了,因為表數量一多,查詢也好,寫入也罷,性能會越來越差。
同時,由于后面的幾種范式在實際項目中應用較少,因此關于這塊的資料也會較少,后續的幾種范式也僅有一些學術機構在琢磨,所以當我們試圖去窺探時,能看到的也是一堆學術詞匯。
除開聊到的六大范式外,還有一種范式名為域鍵范式,也被稱之為終極范式,但目前也僅有學術機構在研究,在生產環境中實際的用途也不大,諸位有興趣可以自己看看,最后再上一個各范式之間的遞進關系圖:
[圖片上傳失敗...(image-f18359-1670572692821)]
- 第一范式:原子性,每個字段的值不能再分。
- 第二范式:唯一性,表內每行數據必須描述同一業務屬性的數據。
- 第三范式:獨立性,表中每個非主鍵字段之間不能存在依賴性。
- 巴斯范式:主鍵字段獨立性,聯合主鍵字段之間不能存在依賴性。
- 第四范式:表中字段不能存在多值依賴關系。
- 第五范式:表中字段的數據之間不能存在連接依賴關系。
- 域鍵范式:試圖研究出一個庫表設計時的終極完美范式。
三、數據庫反范式設計
? ?遵循數據庫范式設計的結構優點很明顯,它避免了大量的數據冗余,節省了大量存儲空間,同時讓整體結構更為優雅,能讓SQL
操作更加便捷且減少出錯。但隨著范式的級別越高,設計出的結構會更加精細化,原本一張表的數據會被分攤到多張表中存儲,表的數量隨之越來越多。
? ?但隨之而來的不僅僅只有好處,也存在一個致命問題,也就是當同時需要這些數據時,只能采用聯表查詢的形式檢索數據,有時候甚至為了一個字段的數據,也需要做一次連表查詢才能獲得。這其中的開銷無疑是花費巨大的,尤其是當連接的表不僅兩三張而是很多張時,有可能還會造成索引失效,這種情況帶來的資源、時間開銷簡直是一個噩夢,這會嚴重地影響整個業務系統的性能。
因此,也正是由于上述一些問題,在設計庫表結構時,我們不一定要
100%
遵守范式準則。這種違反數據庫范式的設計方法,就被稱之為 反范式設計。
? ?遵循范式設計也好,反范式設計也罷,本身兩者之間并沒有優劣之分,只要能夠對業務更有利,那就可以稱之為好的設計方案。范式的目的僅在于讓我們設計的結構更優雅合理,有時候在表中多增加一個字段,從數據庫的角度來看,數據會存在冗余問題,會讓表結構違反范式的定義,但如若能夠在實際情況中減少大量的連表查詢,這種設計自然也是可取的。
也就是說,在設計時千萬不要拘泥于規則之內,一定要結合實際業務考慮,遵循業務優先的原則去設計結構。
當然,對于反范式設計也無需再用更多的語言去描述了,因為本質上就是一個概念詞,也就是不遵循數據庫范式設計的結構,就被稱為反范式結構。不過要牢記的一點是:不是所有不遵循數據庫范式的結構設計都被稱為反范式,反范式設計是指自己知道會破壞范式,但對業務帶來好處大于壞處時,刻意設計出破壞范式的結構。
隨意設計出的結構,不滿足范式要求,同時還無法給業務上帶來收益的,這并不被稱為反范式設計,反范式設計是一種刻意為之的思想。
四、庫表設計篇總結
? ?在本篇中詳細闡述了DB
庫表設計時的一些思想,也就是范式與反范式設計理論,這些理論僅僅只是一套方法論,實際開發過程中,還是需要根據業務來設計出最合適的結構。在文中提及了六種范式,但一般項目中僅需滿足到第三范式或BC
范式即可,因為這個度剛剛好,再往后就會因為過于精細化設計,導致整體性能反而下降。控制到第三范式的級別,一方面數據不會有太多冗余,第二方面也不會對性能影響過大。
同時,如若打破范式的設定能對業務更有利,那也可以違背范式原則去設計。
? ?不過雖說這些屬于方法論,但認真看下來之后,相信諸位在之后設計庫表結構應該會潛意識的遵循一些范式原則,也會盡量的將表結構設計的更為優雅,從而也能讓咱們在開發過程中,減少調整庫表結構的次數和帶來的影響。
一般而言,庫表結構設計的是否合理,區別如下:
- 不合理的結構設計會造成的問題:
- 數據冗余,會浪費一定程度上的存儲空間
- 不便于常規
SQL
操作(例如插入、刪除),甚至會出現異常
- 合理的結構設計帶來的好處:
- 節省空間,
SQL
執行時能節省內存空間,數據存儲時能節省磁盤空間 - 數據劃分較為合理,
DB
性能整體較高,并且數據也非常完整 - 結構便于維護和進行常規
SQL
操作
- 節省空間,