在實體關係模型中,我們知道有三種關係:一對一、一對多、多對多。這只是概念上的關係,但是在真實的關聯式資料庫中,我們只有外來鍵,並沒有這三種關係,那麼我們就來說一說在關聯式資料庫管理系統中,怎麼實現這三種關係。
一對多
這裡先講解一對多,因為這個關係最簡單。一對多和多對一是一回事,所以就不再提多對一這個詞。一對多的概念是一個物件A會對應多個物件B,而從B的角度看,一個物件B只會對於一個物件A。比如說班級和學生就是一對多關係。一個班級對應多個學生,一個學生只會對於一個班級。
一對多的關係之所以說簡單,是因為RDBMS的外來鍵其實就是表示一對多關係。對於一對多關係,我們只需要在“多”的這個表中建立“一”的外來鍵關聯即可,而“一”這邊的表不需要做任何修改。比如前面說到的班級學生關係。班級表不變,學生表增加班級Id作為外來鍵。
多對多
多對多的關係在資料庫設計時比一對一要常見,所以這裡先說說多對多。多對多是一個物件A對應多個物件B,從B角度看,一個物件B也會對應多個物件A。比如說學生和課程的關係就是多對多關係。一個學生會學習多門課程,一門課程會有多個學生來選修。
在RDBMS中,必須使用中間表來表示多對多的關係。中間表我們可以分成兩種,一種是純粹表示關係的中間表,一種是表示中間實體的中間表。
純粹表示關係的中間表很簡單,只需要兩列:AID和BID,AID以外來鍵關聯到A表的主鍵,BID以外來鍵關聯到B表的主鍵,然後這兩個列組成聯合主鍵。這個中間表純粹是表示多對多關係而存在,在業務上不會有對應的實體與之對應。比如前面提到的學生和課程的關係,如果我們只需要知道哪些學生上哪些課,哪些課有哪些學生選,不需要有更多的資訊的情況下,我們就可以建立“學生課程”中間表,裡面只有學生ID和課程ID兩個欄位。
中間實體是在純粹的中間關係表的基礎上,加上了更多的屬性,從而形成了一個新的實體。比如前面提到的學生和課程的關係,如果我們需要記錄學生選課的時間、學生選擇這門課程後的考試成績,那麼我們就像建立一個“選課”實體,該實體具有如下屬性:
- 選課ID,主鍵
- 學生ID,與學生表做外來鍵關聯
- 課程ID,與課程表做外來鍵關聯
- 選課時間,DateTime型別
- 考試成績,記錄選修該課程後考試的最終成績
這就是一箇中間實體,已經完全脫離了普通的多對多關係中間表,而變成一個實體的形式的存在,所以按照前面部落格中講到的主鍵設計的原則,我們可以單獨建立一個選課ID的列作為資料庫的主鍵,該主鍵本身並沒有業務含義。
一對一
一對一概念上是說一個物件A最多對應一個物件B,從B角度看,也是一個物件B最多對應一個物件A。比如說班主任(教師)和班級的關係,一個班主任最多管理一個班級,一個班級也最多隻有一個班主任。
一對一的關係在資料庫設計中,是使用的最少的關係,因為一般來說,如果兩個實體是一對多關係,那麼我們也可以把這兩個實體合併成一個實體。但是在設計中,我們仍然會遇到兩個完全不同的實體,之間存在一對一關係。
一對一的RDBMS實現是在其中的一個表上建立外來鍵指向另一個表,同時在該外來鍵列上建立唯一約束。比如前面說到的班主任和班級關係,我們可以在班級表建立班主任欄位,然後再在該欄位建立唯一約束。因為每個班都會有班主任,所以班主任欄位是不允許為空的。一個教師可以當某個班的班主任,也可以不當任和班的班主任,同時也不可能在班級表的班主任欄位上出現兩次,所以最多就當一個班的班主任,所以該設計滿足需求。
那麼我們可不可以反過來,在教師表中建立所管理的班級Id欄位,指向班級表,並建立唯一約束呢?除了不滿足“每個班必然有一個班主任”這個業務約束外,其他都沒有問題。所以如果對於一對一的情況,如果那邊必須要求持有另一邊,則就在哪邊增加外來鍵欄位;如果沒有要求必須持有一個另一類實體的話,就哪邊新增外來鍵列都行。
外來鍵與索引
外來鍵是一種約束,與索引的概念不一樣,只是大多數情況下,我們建立外來鍵時,都會在外來鍵列上建立對應的索引。外來鍵的存在會在每一次資料插入、修改時進行約束檢查,如果不滿足外來鍵約束,則禁止資料的插入或修改,這必然帶來一個問題,就是在資料量特別大的情況下,每一次約束檢查必然導致效能的下降。索引其實也有類似的問題,索引如果建多了,那麼在插入刪除修改資料時也要去維護對應的索引,所以索引的存在也會導致資料操作變慢。
不過外來鍵與索引的優點不同,外來鍵只是保證資料的一致性,並不能給系統效能帶來任何好處,所以由於外來鍵導致的插入資料變慢會隨著資料量的增長而越來越嚴重。而索引的目的是為了檢索資料更快,維護資料時導致的索引資料的變更,對效能的影響不會像外來鍵那樣隨著資料量增長而變得嚴重(當然大數量時的索引樹維護會比小資料量的索引樹維護更麻煩,但至少不是像外來鍵那樣)。
出於效能的考慮,如果我們的系統完全由我們開發的程式使用,而不需要提供資料庫給其他應用系統寫入資料,而且對效能要求較高,那麼我們可以考慮在生產環境中不使用外來鍵,只需要建立能夠提高效能的索引。由於整個資料庫的操作都是由我們開發的程式來完成的,所以我們程式可以在開發過程中做好各方面的一致性檢查,保證操作的資料是滿足外來鍵約束的,而不需要真正的存在這樣一個外來鍵約束。怎麼做到這一點呢,首先,我們在建立資料庫時有多個指令碼,包括建立表、建立初始化資料、建立索引、建立外來鍵等,我們在開發和測試環境中,都把這些指令碼執行了,以使開發測試環境中的資料庫是完整的,經過大量測試保證應用程式能夠維護資料之間的約束的情況下,那麼我們在生產時,並不需要執行建立外來鍵這個指令碼檔案,只需要建立表、初始化資料、建立索引等即可。