按照第三范式的要求,是不應該存在冗余字段的,但有時候冗余字段可以提高性能,是可以使用的。
什么是冗余字段?
在設計數據庫時,某一字段屬於一個表,但它又同時出現在另一個或多個表,且完全等同於它在其本來所屬表的意義表示,那么這個字段就是一個冗余字段。
-------------------------------------------------------------
加深理解部分
在設計數據庫的時候,應該注意一下什么呢?首先來看一張表。
學生成績表
姓名 | 性別 | java成績 | c成績 | 班級 |
張三 | 男 | 90 | 80 | 一班 |
李四 | 女 | 80 | 90 | 一班 |
楊過 | 男 | 90 | 80 | 一班 |
思考:上面這張表在設計上會出現什么問題?
作為初學者看到這章表是回答不出什么問題,其實從現實生活中來講,這的確沒什么問題的,記得念中學的時候,每次考完試,心里都很着急,急什么呢?當然是成績,想想那個時候老師又是怎樣把你的成績信息給列出來的呢?是不是以這樣的方式,每個班,甚至整個學校的學生成績都是使用一張表來列出來的,而我們每個人的成績利用該表中的一行數據來表示的,其中包括一個學生的學號,姓名,班級,成績以及排名等等。而這里我們只是專業化成了數據庫,一個班級的學生成績變成了數據庫中的一張表,而每個學生的成績變成了表中的一條記錄而已,所以認為沒什么問題。但是對數據庫的設計來講,概念就不一樣了,這樣設計就會很容易出現很多問題。會出現一下什么問題呢。比如像增加異常,刪除異常,更新異常等等,下面是幾個例子。
eg1:在增加數據的時候,可能發生錯誤。這叫增加異常
姓名 | 性別 | java成績 | C成績 | 班級 |
張三 | 男 | 90 | 80 | 一班 |
李四 | 女 | 80 | 90 | 一班 |
楊過 | 男 | 90 | 80 | 一班 |
小龍女 | 女 | 100 | 70 | 1班 |
再仔細看上面這張表,在插入最后一條數據的時候,其中插入的班級和前面的不同,即“1班”,這從業務上來講,當然沒什么問題,但是從數據庫的角度來講,確是兩個完全不同的概念,你可以認為是數據庫本身太笨了吧,所以這個時候插入的數據,會給我們帶來不必要的錯誤,這種錯叫做增加異常。
eg2:在刪除數據時,可能會刪除相關的其它數據,這叫刪除異常
這句話怎么理解呢?同樣,看上面eg1的表,如果我們現在要指定刪除班級為“一班”的班級怎么辦?這個時候我們使用delete 學生成績表 where 班級='一班' 的話,就刪除了班級='一班'的這些記錄,這個時候,不是也給我們帶來了不必要的后果嗎?這就是刪除異常。
eg3:在更新數據的時候,可能會有數據沒有更新到。這叫更新異常。
姓名 | 性別 | java成績 | C成績 | 班級 |
張三 | 男 | 90 | 80 | 1班 |
李四 | 女 | 80 | 90 | 1班 |
楊過 | 男 | 90 | 80 | 一班 |
那如何理解更新異常呢?先了解上面表在做什么吧,上面表是在修改班級,我想把班級名稱全部改成“1班”,這個時候我會使用update語句來一條一條的修改,但是如果數據有很多很多的話,這個時候我們有可能忽略了某個地方,導致沒有修改到,所以在我執行某型操作的時候,就會出現數據與我們想象的不一致,這種結果就是所謂的更新異常導致的。上面舉了幾個例子,說明了在數據庫中是很容易出現問題的,而這些問題從業務上來講,我們很難查找出來,那導致這樣的問題原因是什么呢?有兩點:
1.看上面的表,把他們的數據全部耦合到一起了。
2.因為我這樣耦合的設計,導致產生了許多重復的數據,比如班級列,我就添加了三條一模一樣的,也就是一班。解決方法:根據實體和關系,我們可以把他們保存在不同的表中,然后可以利用一個外鍵,來指向我們所需要的數據。據上面例子,我們可以建立兩張表:
表1:
班級 | 班級編號 |
一班 | 001 |
二班 | 002 |
表2:
學號 | 姓名 | 班級編號 |
0103 | 張三 | 001 |
0104 | 李四 | 001 |
0201 | 楊過 | 002 |
0202 | 小龍女 | 002 |
這張表,就是把班級和學生信息分開來保存了,這個時候,班級和學生之間的關系是一對多,所以在數據庫設計的時候,我們可以將一對多中,多的那一方設置一個外鍵列,這里當然是學生是多的這方,所以我們在表中有一列叫做“班級編號”的外鍵列,然后利用外鍵列去引用我們的班級表中的“班級編號”列,從而建立起他們一件的那種一對多的關系。上面的問題是解決了,但是在以后我相信還會遇到相同的問題,也就是會遇到什么增加,刪除或更新異常等,這就代表了以后的數據“難以維護”。所以我們要對數據庫進行適當的設計,才可以大大的增加可維護性。那么進行數據庫設計,有六個范式,一般說來,數據庫只需滿足第三范式(3NF)就行了。什么是范式呢?為了將各種信息進行有效的分類整理,就設計了一套行之有效的數據庫設計原則,這叫做范式,英文normal forms,意思是規范化表。也表示將表進行分類處理,這個過程就叫做規范化。簡單理解,規范化表的過程,就是將數據進行分類的過程。下面說說這三個范式。
1 第一范式(1NF)
在任何一個關系數據庫中,第一范式(1NF)是對關系模式的基本要求,不滿足第一范式(1NF)的數據庫就不是關系數據庫。所謂第一范式(1NF)是指數據庫表的每一列都是不可分割的基本數據項,同一列中不能有多個值,即實體中的某個屬性不能有多個值或者不能有重復的屬性。如果出現重復的屬性,就可能需要定義一個新的實體,新的實體由重復的屬性構成,新實體與原實體之間為一對多關系。在第一范式(1NF)中表的每一行只包含一個實例的信息。例如,對於員工信息表,不能將員工信息都放在一列中顯示,也不能將其中的兩列或多列在一列中顯示;員工信息表的每一行只表示一個員工的信息,一個員工的信息在表中只出現一次。簡而言之,第一范式就是無重復的行,並且列不可再分。
2 第二范式(2NF)
第二范式(2NF)是在第一范式(1NF)的基礎上建立起來的,即滿足第二范式(2NF)必須先滿足第一范式(1NF)。第二范式(2NF)要求數據庫表中的每個實例或行必須可以被惟一地區分。為實現區分通常需要為表加上一個列,以存儲各個實例的惟一標識。員工信息表中加上了員工編號(emp_id)列,因為每個員工的員工編號是惟一的,因此每個員工可以被惟一區分。這個惟一屬性列被稱為主關鍵字或主鍵、主碼。第二范式(2NF)要求實體的屬性完全依賴於主關鍵字。所謂完全依賴是指不能存在僅依賴主關鍵字一部分的屬性,如果存在,那么這個屬性和主關鍵字的這一部分應該分離出來形成一個新的實體,新實體與原實體之間是一對多的關系。為實現區分通常需要為表加上一個列,以存儲各個實例的惟一標識。簡而言之,第二范式就是非主鍵列必須依賴於主鍵列。第二范式(2NF)值得注意的是:“NF2要求在數據庫設計時,每個表中的信息,只能是自己實體所擁有的信息。”
eg:屬性1必須依賴於屬性2:屬性1,一定是屬性2所代表的實體“天生”具有的。
學號 | 姓名 | 興趣1 | 興趣2 |
1 | a | 打球 | 打豆豆 |
2 | b | 開車 | 開玩笑 |
對這張表來說就是:姓名必須依賴於學號:姓名,一定是學號所代表的實體“天生”具有的如果想這張表不是屬於學號所代表的實體,即學生,那么就應該將學生信息和興趣分開來建表保存。數據庫設計時,基本上都要滿足NF2.那什么時候會違反NF2呢? 有一個需求,要求查詢:學號,姓名,班級名稱。由於學生姓名和班級名稱分開在2個中,所以在查詢時,必須使用表連接。代碼:select * from 班級表 c , 學生表 s where c.班級編號=s.班級編號。但是表連接會讓計算機做大量的匹配和計算,所以是比較消耗資源(資源主要是指CPU和內存)。為了提高效率,所以可以違反NF2,將表格如下:
班級表 | |
班級編號 | 班級名稱 |
學生表 | |||
學號 | 姓名 | 班級編號 | 班級姓名 |
現在查詢就只需要查詢一個表了,從而避免了表連接!學生表中的“班級名稱”是第2次出現,所以叫做“冗余字段”,這種設計方法叫做“逆范式”!在本質上,實際上就是犧牲空間,換取時間。
3 第三范式(3NF)
在滿足了第一范式和第二范式的基礎只上,可以滿足第三范式,第三范式簡而言之,就是非主鍵列之間是相對獨立的。先看一張表。
學號 | 姓名 | 出生日期 | 生肖 |
1 | 張三 | 1980-01-02 | 猴 |
2 | 李四 | 1983-04-02 | 牛 |
3 | 王五 | 1988-11-02 | 牛 |
生肖表
生肖編號 | 生肖名稱 |
1 | 猴 |
2 | 牛 |
這里的生肖其實是和時間有關系了,可以通過時間來確定生肖是什么,所以這里的生肖完全可以忽略不計,一般這樣設計是為了減輕數據庫的計算壓力,所以才保留此字段的,也就是前面提到的“冗余字段”,逆范式。所以由此可以推出3NF是完全可以違反的。最后總結一下前三個范式那些必須滿足:
NF1 行不可重復,列不可再分 - 必須滿足
NF2 非主鍵列必須依賴於主鍵列 - 必須滿足
NF3 非主鍵列之間必須相互獨立 - NF3可以不滿足