天天看點

關于資料庫主鍵和外鍵

一、什麼是主鍵、外鍵:

關系型資料庫中的一條記錄中有若幹個屬性,若其中某一個屬性組(注意是組)能唯一辨別一條記錄,該屬性組就可以成為一個主鍵 

比如  

學生表(學号,姓名,性别,班級) 

其中每個學生的學号是唯一的,學号就是一個主鍵 

課程表(課程編号,課程名,學分) 

其中課程編号是唯一的,課程編号就是一個主鍵 

成績表(學号,課程号,成績) 

成績表中單一一個屬性無法唯一辨別一條記錄,學号和課程号的組合才可以唯一辨別一條記錄,是以 學号和課程号的屬性組是一個主鍵 

成績表中的學号不是成績表的主鍵,但它和學生表中的學号相對應,并且學生表中的學号是學生表的主鍵,則稱成績表中的學号是學生表的外鍵 

同理 成績表中的課程号是課程表的外鍵 

定義主鍵和外鍵主要是為了維護關系資料庫的完整性,總結一下:

1.主鍵是能确定一條記錄的唯一辨別,比如,一條記錄包括身份正号,姓名,年齡。

身份證号是唯一能确定你這個人的,其他都可能有重複,是以,身份證号是主鍵。 

2.外鍵用于與另一張表的關聯。是能确定另一張表記錄的字段,用于保持資料的一緻性。

比如,a表中的一個字段,是b表的主鍵,那他就可以是a表的外鍵。

二、  主鍵、外鍵和索引的差別 

主鍵、外鍵和索引的差別?

關于資料庫主鍵和外鍵

聚集索引和非聚集索引的差別?

聚集索引一定是唯一索引。但唯一索引不一定是聚集索引。  

聚集索引,在索引頁裡直接存放資料,而非聚集索引在索引頁裡存放的是索引,這些索引指向專門的資料頁的資料。

三、資料庫中主鍵和外鍵的設計原則

主鍵和外鍵是把多個表組織為一個有效的關系資料庫的粘合劑。主鍵和外鍵的設計對實體資料庫的性能和可用性都有着決定性的影響。

必須将資料庫模式從理論上的邏輯設計轉換為實際的實體設計。而主鍵和外鍵的結構是這個設計過程的症結所在。一旦将所設計的資料庫用于了生産環境,就很難對這些鍵進行修改,是以在開發階段就設計好主鍵和外鍵就是非常必要和值得的。

主鍵:

  關系資料庫依賴于主鍵---它是資料庫實體模式的基石。

  主鍵在實體層面上隻有兩個用途:

        1. 惟一地辨別一行。

        2. 作為一個可以被外鍵有效引用的對象。

  基于以上這兩個用途,下面給出了我在設計實體層面的主鍵時所遵循的一些原則:

        1. 主鍵應當是對使用者沒有意義的。如果使用者看到了一個表示多對多關系的連接配接表中的資料,并抱怨它沒有什麼用處,那就證明它的主鍵設計地很好。

        2. 主鍵應該是單列的,以便提高連接配接和篩選操作的效率。

        注:使用複合鍵的人通常有兩個理由為自己開脫,而這兩個理由都是錯誤的。其一是主鍵應當具有實際意義,然而,讓主鍵具有意義隻不過是給人為地破壞資料庫提供了友善。其二是利用這種方法可以在描述多對多關系的連接配接表中使用兩個外部鍵來作為主鍵,我也反對這種做法,理由是:複合主鍵常常導緻不良的外鍵,即當連接配接表成為另一個從表的主表,而依據上面的第二種方法成為這個表主鍵的一部分,然,這個表又有可能再成為其它從表的主表,其主鍵又有可能成了其它從表主鍵的一部分,如此傳遞下去,越靠後的從表,其主鍵将會包含越多的列了。

        3. 永遠也不要更新主鍵。實際上,因為主鍵除了惟一地辨別一行之外,再沒有其他的用途了,是以也就沒有理由去對它更新。如果主鍵需要更新,則說明主鍵應對使用者無意義的原則被違反了。

       注:這項原則對于那些經常需要在資料轉換或多資料庫合并時進行資料整理的資料并不适用。

        4. 主鍵不應包含動态變化的資料,如時間戳、建立時間列、修改時間列等。

        5. 主鍵應當有計算機自動生成。如果由人來對主鍵的建立進行幹預,就會使它帶有除了惟一辨別一行以外的意義。一旦越過這個界限,就可能産生認為修改主鍵的動機,這樣,這種系統用來連結記錄行、管理記錄行的關鍵手段就會落入不了解資料庫設計的人的手中。

四、資料庫主鍵選取政策

我們在建立資料庫的時候,需要為每張表指定一個主鍵,所謂主鍵就是能夠唯一辨別表中某一行的屬性或屬性組,一個表隻能有一個主鍵,但可以有多個候選索引。因為主鍵可以唯一辨別某一行記錄,是以可以確定執行資料更新、删除的時候不會出現張冠李戴的錯誤。當然,其它字段可以輔助我們在執行這些操作時消除共享沖突,不過就不在這裡讨論了。主鍵除了上述作用外,常常與外鍵構成參照完整性限制,防止出現資料不一緻。是以資料庫在設計時,主鍵起到了很重要的作用。

常見的資料庫主鍵選取方式有:

· 自動增長字段

· 手動增長字段

· uniqueidentifier

· “comb(combine)”類型

1自動增長型字段

很多資料庫設計者喜歡使用自動增長型字段,因為它使用簡單。自動增長型字段允許我們在向資料庫添加資料時,不考慮主鍵的取值,記錄插入後,資料庫系統會自動為其配置設定一個值,確定絕對不會出現重複。如果使用sql server資料庫的話,我們還可以在記錄插入後使用@@identity全局變量擷取系統配置設定的主鍵鍵值。

盡管自動增長型字段會省掉我們很多繁瑣的工作,但使用它也存在潛在的問題,那就是在資料緩沖模式下,很難預先填寫主鍵與外鍵的值。假設有兩張表:

order(orderid, orderdate)

orderdetial(orderid, linenum, productid, price)

order表中的orderid是自動增長型的字段。現在需要我們錄入一張訂單,包括在order表中插入一條記錄以及在orderdetail表中插入若幹條記錄。因為order表中的orderid是自動增長型的字段,那麼我們在記錄正式插入到資料庫之前無法事先得知它的取值,隻有在更新後才能知道資料庫為它配置設定的是什麼值。這會造成以下沖突發生:

首先,為了能在orderdetail的orderid字段中添入正确的值,必須先更新order表以擷取到系統為其配置設定的orderid值,然後再用這個orderid填充orderdetail表。最後更新oderdetail表。但是,為了確定資料的一緻性,order與orderdetail在更新時必須在事務保護下同時進行,即確定兩表同時更行成功。顯然它們是互相沖突的。

除此之外,當我們需要在多個資料庫間進行資料的複制時(sql server的資料分發、訂閱機制允許我們進行庫間的資料複制操作),自動增長型字段可能造成資料合并時的主鍵沖突。設想一個資料庫中的order表向另一個庫中的order表複制資料庫時,orderid到底該不該自動增長呢?

ado.net允許我們在dataset中将某一個字段設定為自動增長型字段,但千萬記住,這個自動增長字段僅僅是個占位符而已,當資料庫進行更新時,資料庫生成的值會自動取代ado.net配置設定的值。是以為了防止使用者産生誤解,建議大家将ado.net中的自動增長初始值以及增量都設定成-1。此外,在ado.net中,我們可以為兩張表建立datarelation,這樣存在級聯關系的兩張表更新時,一張表更新後另外一張表對應鍵的值也會自動發生變化,這會大大減少了我們對存在級聯關系的兩表間更新時自動增長型字段帶來的麻煩。

2手動增長型字段

既然自動增長型字段會帶來如此的麻煩,我們不妨考慮使用手動增長型的字段,也就是說主鍵的值需要自己維護,通常情況下需要建立一張單獨的表存儲目前主鍵鍵值。還用上面的例子來說,這次我們建立一張表叫intkey,包含兩個字段,keyname以及keyvalue。就像一個hashtable,給一個keyname,就可以知道目前的keyvalue是什麼,然後手工實作鍵值資料遞增。在sql server中可以編寫這樣一個存儲過程,讓取鍵值的過程自動進行。代碼如下:

create procedure [getkey]

@keyname char(10), 

@keyvalue int output 

as

update intkey set @keyvalue = keyvalue = keyvalue + 1 where keyname = @keyname

go

這樣,通過調用存儲過程,我們可以獲得最新鍵值,確定不會出現重複。若将orderid字段設定為手動增長型字段,我們的程式可以由以下幾步來實作:首先調用存儲過程,獲得一個orderid,然後使用這個orderid填充order表與orderdetail表,最後在事務保護下對兩表進行更新。

使用手動增長型字段作為主鍵在進行資料庫間資料複制時,可以確定資料合并過程中不會出現鍵值沖突,隻要我們為不同的資料庫配置設定不同的主鍵取值段就行了。但是,使用手動增長型字段會增加網絡的roundtrip,我們必須通過增加一次資料庫通路來擷取目前主鍵鍵值,這會增加網絡和資料庫的負載,當處于一個低速或斷開的網絡環境中時,這種做法會有很大的弊端。同時,手工維護主鍵還要考慮并發沖突等種種因素,這更會增加系統的複雜程度。

3使用uniqueidentifier

sql server為我們提供了uniqueidentifier資料類型,并提供了一個生成函數newid( ),使用newid( )可以生成一個唯一的uniqueidentifier。uniqueidentifier在資料庫中占用16個位元組,出現重複的機率非常小,以至于可以認為是0。我們經常從系統資料庫中看到類似

{45f0eb02-0727-4f2e-aab5-e8aedee0cec5}

的東西實際上就是一個uniqueidentifier,windows用它來做com元件以及接口的辨別,防止出現重複。在.net裡管uniqueidentifier稱之為guid(global unique identifier)。在c#中可以使用如下指令生成一個guid:

guid u = system.guid.newguid();

對于上面提到的order與orderdetail的程式,如果選用uniqueidentifier作為主鍵的話,我們完全可以避免上面提到的增加網絡roundtrip的問題。通過程式直接生成guid填充主鍵,不用考慮是否會出現重複。

uniqueidentifier字段也存在嚴重的缺陷:首先,它的長度是16位元組,是整數的4倍長,會占用大量存儲空間。更為嚴重的是,uniqueidentifier的生成毫無規律可言,要想在上面建立索引(絕大多數資料庫在主鍵上都有索引)是一個非常耗時的操作。有人做過實驗,插入同樣的資料量,使用uniqueidentifier型資料做主鍵要比使用integer型資料慢,是以,出于效率考慮,盡可能避免使用uniqueidentifier型資料庫作為主鍵鍵值。

4使用“comb(combine)”類型

既然上面三種主鍵類型選取政策都存在各自的缺點,那麼到底有沒有好的辦法加以解決呢?答案是肯定的。通過使用comb類型(資料庫中沒有comb類型,它是jimmy nilsson在他的“the cost of guids as primary keys”一文中設計出來的),可以在三者之間找到一個很好的平衡點。

comb資料類型的基本設計思路是這樣的:既然uniqueidentifier資料因毫無規律可言造成索引效率低下,影響了系統的性能,那麼我們能不能通過組合的方式,保留uniqueidentifier的前10個位元組,用後6個位元組表示guid生成的時間(datetime),這樣我們将時間資訊與uniqueidentifier組合起來,在保留uniqueidentifier的唯一性的同時增加了有序性,以此來提高索引效率。也許有人會擔心uniqueidentifier減少到10位元組會造成資料出現重複,其實不用擔心,後6位元組的時間精度可以達到1/300秒,兩個comb類型資料完全相同的可能性是在這1/300秒内生成的兩個guid前10個位元組完全相同,這幾乎是不可能的!

每天一點點,感受自己存在的意義。