在資料庫設計時,主要就是對實體和關係的設計,實體表現出來就是表,關係表現出來就是外來鍵。而對於一個表,由兩部分組成:主鍵和屬性。主鍵的簡單定義就是表中為每一行資料的唯一標識。其實更準確的說法,每一行資料的唯一標識是候選鍵(Candidate Key),一個表中可以有很多個候選鍵,主鍵是候選鍵中的一個,主要用於更方便的檢索和管理資料。一個表中可以有多個候選鍵,但是隻有一個主鍵。由於主鍵常常用於檢索資料,也用於表之間的關聯,所以主鍵的設計的好壞將會嚴重影響資料操作的效能。下面來介紹下主鍵設計的幾個考慮因素。
主鍵的資料型別
最常見的主鍵資料型別是數字型別、固定長度的字元型別和GUID型別。通常情況下,RDBMS會在主鍵上建立聚集索引(SQL Server預設都這麼做),由於我們使用B-Tree的資料結構來儲存索引資料,所以一般對主鍵有以下兩個要求:
- 越短越好——越短在一個Page中儲存的節點越多,檢索速度就越快。
- 順序增長——如果每一條插入的資料的主鍵都比前面的主鍵大,那麼B-Tree上的節點也是順序增長的,不會造成頻繁的B-Tree分割。
越短越好是為了查詢的速度快,順序增長是為了插入速度快。
有了這兩個要求,我們再來分析下各個資料型別:
- 數字型別:根據資料量決定是用Int16還是Int32或者Int64,能用Int32的就不需要使用Int64。
- 字元型別:基本不滿足前面提到的2點要求,字元型別一般不會很短,而且也很可能不是順序增長的,所以不是特別推薦的主鍵型別。當然如果確實業務需求使用字元型別,那麼也儘量使用char(XX)而不要使用varchar(XX),因為在RDBMS中,對於定長字串和變成字串的資料結構和處理是不一樣的,varchar的效能更差。
- GUID型別:這個型別並不是所有資料庫都有對應的資料型別,SQL Server有uniqueidentifier,MySQL沒有。GUID型別在SQL Server中是16個位元組,不算短,比4個位元組的Int32長多了。在插入新資料時,GUID一般都是使用NewId()這樣的生成隨機GUID的方式生成的,所以也不是順序增長的,在插入速度上不會很快。
通過上面的比較,我們知道使用數字型別是更好的方式,那麼我們為什麼還會有人使用GUID和字串來當主鍵呢?那是因為:
相對於數字型別,字元型別更易讀易記,在檢索關聯的資料時,更方便直接。
GUID的優勢是全球唯一,也就是說同樣的系統,如果部署了多套環境,那麼裡面的資料的主鍵仍然是唯一的,這樣有助於資料的整合。典型的例子就是一個系統在全國每個省份都部署一套,每個省份的資料各種錄入,互不干擾,然後再把每個省的資料整合起來為總部做分析。
資料庫主鍵與業務主鍵
前面說到一個表可能有很多個唯一標識的候選鍵,那麼這麼多候選鍵中,哪個應該拿來做主鍵呢?一種方案是再新建一個獨立的欄位作為主鍵,該欄位並沒有業務含義,只是一個自增列或者流水號,用於唯一標識每一行資料,這是資料庫主鍵。另外一種方案是選擇其中較短較常用的屬性作為主鍵,這是業務主鍵。個人建議是不要使用任何有業務含義的欄位作主鍵,而是使用一個自增的(或者系統生成的)沒有實際業務意義的欄位作為主鍵。為什麼呢?主要是出於以下考慮:
具有業務意義的欄位很可能是使用者從系統錄入的,不要信任使用者的任何輸入,只要是使用者自己錄入的,那麼就很有可能錄錯了,如果發現錄入錯誤,這個時候再對主鍵進行修改,將會涉及到大量關聯的外來鍵表的修改,是很麻煩的一件事情。比如在做人員表的時候,就不要使用員工號或者身份證號做主鍵。
具有業務意義的欄位雖然在當前階段是唯一的,是不變的,但是並不能保證隨著公司政策變動、業務調整等原因,導致該業務欄位需要修改,以滿足新的業務要求,這個時候要修改主鍵也是很麻煩的事情。比如部門表,我們以部門Code作為主鍵,但是後來部門變動,Code修改,則系統部門表的主鍵也得更改。
還有一個原因是業務主鍵在資料錄入的時候不一定是明確知道的,有時我們會在不知道業務主鍵的情況下,就錄入其他相關資訊,這個時候,如果使用業務主鍵做資料庫的主鍵,那麼資料將無法錄入。比如員工表把員工號作為主鍵,那麼員工還沒有入職,沒有員工號的時候,HR需要先維護一些該預入職員工的資訊是不可能的。
聯合主鍵
聯合主鍵就是以多個欄位來唯一標識每一行資料。前面已經說到主鍵應該越短越好,而且是建議是一個沒有意義的自增列,那麼是不是就不會再需要聯合主鍵呢?答案是否定的,我們仍然可能會使用到聯合主鍵。聯合主鍵主要使用在多對多的關係時,中間表就需要使用聯合主鍵。在簡單的多對多關係中,我們不需要為中間的關聯建立實體,所以中間表可能就只需要兩列,分別是兩個實體表的主鍵。
主鍵值的生成
主鍵值的生成可以參考NHibernate的配置,概況下來主要有這麼幾種生成方式:
- 自增,這是SQL Server常用的主鍵生成方式,完全由資料庫管理主鍵的值。
- Sequence物件,這是Oracle常用的主鍵生成方式,現在SQL Server已支援。主要是在資料庫中有一個Sequence物件,通過該物件生成主鍵。
- GUID,這是用於GUID型別的主鍵,可以使用newid()這種資料庫提供的函式,或者使用程式生成Guid並賦值。
- Hilo值,這是一種使用高低位演算法生成的數字值的主鍵。該值由NHibernate程式內部生成。
- 其他程式賦值,完全由程式根據自己的演算法生成並賦值。
更詳細的主鍵生成,我們可以參見:http://www.cnblogs.com/chenkai/archive/2009/04/13/1434912.html
主鍵與索引
在概念和作用上,主鍵與索引是完全兩個不同的東西,但是由於我們大部分情況下都是使用主鍵檢索資料,所以大部分資料庫的預設實現,在建立主鍵時會自動建立對應的索引。
以SQL Server為例,預設情況下,建立主鍵的列,就會建立聚集索引,但是實際上,我們可以在建立主鍵時不使用聚集索引。另外還有一個唯一約束(索引)的概念,該索引中的資料必須是唯一不能重複的,感覺和主鍵的意義一樣,但是還是有一點點區別。
主鍵是隻能由一個,而唯一約束(索引)在一個表中可以有多個。
主鍵不能為空,而唯一約束(索引)是可以為空的。