數據庫主鍵設計思考-主鍵故事

      有這樣一個關於數據庫主鍵的故事,講的挺有道理,可以加深對主鍵設計的理解!   

  

      1969年8月8日,在北京協和醫院降生了一個漂亮的小女孩。接生的阿姨說,她的聲音這麼大,好象想要全世界的人都聽到。

      後來,她的父親爲她取了一個很好聽的名字,叫“王菲”。於是,所有的小朋友就叫她“王菲”,“王菲”就是她童年的主鍵。

      在她上初二的時候,認識了二班另一個叫“王菲”的同學,而且和她同一天生日。不過,同學們常常將她倆弄錯,後來,就分別叫她們“大王菲”和“小王菲”。“大王菲”就是她在同學們心目中的主鍵。

      在大王菲18歲的那一年,她領到了她的身份證。從此,她有了新的身份標識“100321690808022”,這一標識可以唯一區別中國大陸的每一個人。同時,原來二班的“小王菲”也領到了她自己的身份證,“100321690808006”。於是,人們就可以用身份證號,唯一標識兩個“王菲”了。身份證號就是她成年後的主鍵。

      由於她的歌唱的非常好,沒多久就成了歌星。後來她去了香港發展,娛樂公司非要將她的名字更改爲“王靖雯”,雖然她並不喜歡這個名字,但爲了事業的發展還是同意了。“王靖雯”就成了她在娛樂圈裏的主鍵。

     “王靖雯”的歌打動了許許多多的歌迷,很快就成了歌壇天后。歌迷們將“王靖雯”這一主鍵與無數條動聽的歌曲記錄關聯在了一起,並形成了一個龐大的歌迷數據庫。

      沒多久,王靖雯和一個彈電吉他的小子相愛了。那小子說,還是“王菲”這個名字好聽,於是,“王靖雯”又變回“王菲”了。主鍵被那小子修改了,這下麻煩大了。歌迷們都糊塗了,是將她的歌關聯到“王菲”還是“王靖雯”呢?這一主鍵的修改,導致歌迷數據庫的一次大混亂。

      再後來呢,她和那個彈電吉他的小子結婚了,香港政府將他們的身份證號碼,用結婚證書關聯起來。這本也算一樁好事,可是,月老在酒醒之後發現了這一錯誤,就將關聯的記錄刪除了。陰差陽錯,命運使然,她和那個彈電吉他的小子只好分手了。

      但是,正如接生的阿姨說的那樣,她的聲音的確讓全世界的人都聽到了!

      講完這個故事之後,我們是否能領悟到數據庫設計的一些哲理呢?

      什麼是主鍵?

      關係數據庫說,爲了唯一區分表的每一行記錄,必須爲表確定一個主鍵。主鍵可以是一個或多個列組成,這些主鍵列的值不能重複。一個表只能有一個主鍵,但可以有多個候選索引。因爲主鍵可以唯一標識某一行記錄,所以可以確保執行數據更新、刪除的時候不會出現張冠李戴的錯誤。主鍵是兩個表進行關聯的基礎,所謂“關係”體現的是一個表的字段與另一個表的主鍵的關聯,防止出現數據不一致。所以數據庫在設計時,主鍵起到了很重要的作用。     

      在日常的應用項目開發中,我們常常都會面臨“該拿什麼字段來做主鍵”的難題。

      用名字做主鍵?一班的“王菲”和二班的“王菲”可能重複。

      用身份證號做主鍵?在大陸也許不會重複,去香港後,人家卻不認。本不相信第二代身份證號會重複,但俺的一位同事正好撞上!

      同樣,主鍵值的更新也是麻煩事兒。“王靖雯”變成“王菲”那段日子,貨架上一張唱片是王靖雯的,另一張又是王菲的,許多歌迷還以爲又有新人出現了。

      請不要說可以級聯更新,將你那個做主鍵的字段加長兩位試試?想當年,當身份證號從15位增加到18位時,給銀行、證券、政府、企業...等多條戰線上的兄弟們製造了怎樣的痛苦啊!

      爲什麼會出現這些問題呢?

      原因就是:錯誤地把對象屬性當作對象標識!

      對象屬性和對象標識都是對象數據庫中的概念。

      什麼是對象屬性呢?

      就是業務邏輯上涉及的任何可變信息,什麼“姓名”、“性別”、“身份證號”、“訂單號”...通通都是對象屬性。對象屬性總會變化的,只是有些變得快,有些變得慢而已。

      對象標識是啥?就是唯一區分數據對象的鑑別符,對象標識存在的唯一目的就是區分對象,除此之外沒有任何業務邏輯上的意義。

      不管王菲的屬性值怎樣變化,但王菲還是王菲,不是二班的那個“王菲”。也就是說,王菲的靈魂未變,她是不會改變的,就象哲學上所說的“不以人的意志爲轉移”。這種唯一表示對象本身的東西,就是對象標識!

      對象標識是唯一的。也就是說,即使兩個對象,他們的屬性完全一樣,但它們的對象標識是不同的。畢竟,同名同姓甚至同一天出生的大王菲和小王菲是兩個不同的人。

      對象的標識是永恆不變的。一旦對象產生,它的標識就自然地、唯一地產生了。儘管王菲換了名,身份證號也變過,但王菲的對象標識未變。即使到了下個世紀,她的對象標識也將依然存在於歌迷們的們的心中。

      對象的標識是描述關係的基礎。王菲唱的歌是王菲唱的,不是初二二班的那個“王菲”唱的。王靖雯唱的歌就是王菲唱的歌,有的歌迷只將歌曲和歌手的人名關聯起來,難怪會出混亂。香港政府也犯相同的錯誤,將王菲的身份證號碼這一內部屬性,跟那個彈電吉他的小子關聯起來,也許就是命運的錯誤。

      那麼,我們在設計數據庫結構時,到底該用什麼來做主鍵呢?

      對象數據庫說,主鍵只能是對象標識!

      至於對象屬性是否唯一,那是由業務邏輯所決定的。如果業務邏輯規定訂單號不能重複,就爲訂單號建一個唯一索引好了,但它不是主鍵。

      所以,當我們看到有些數據庫設計採用了額外的一個字段來專門充當主鍵,並用這個主鍵與其他表關聯的話,那就是已經走到對象數據庫的門口了。什麼“內部碼”,“流水號”,“序列號”,“自增數”...通通都可以算是對象標識。

      爲什麼SQL Server要提供自增量字段以及GUID的標識字段呢?都是爲了這專門的主鍵字段服務的。

      如果我們打算向對象數據庫路上走得話,就請使用對象標識來做專門的主鍵字段吧。

      當然,怎樣產生唯一的對象標識來做主鍵,這也是有說道的。

      1.用自增量字段

      自增量字段每次都會按順序遞增,可以保證在一個表裏的主鍵不重複。除非超出了自增字段類型的最大值並從頭遞增,但這幾乎不可能。使用自增量字段來做主鍵是非常簡單的,一般只需在建表時聲明自增屬性即可。

      自增量字段的長度可以很短,比如使用一個int類型就基本上夠用了。簡短的主鍵可以在大量數據和複雜的關係查詢中表現出更好的性能。

      自增量的值都是需要在系統中維護一個全局的數據值,每次插入數據時即對此次值進行增量取值。當在當量產生唯一標識的併發環境中,每次的增量取值都必須最此全局值加鎖解鎖以保證增量的唯一性。這可能是一個併發的瓶頸,會牽扯一些性能問題。

      還有,如果要搞分佈式數據庫的話,這自增量字段就有問題了。因爲,在分佈式數據庫中,不同數據庫的同名的表可能需要進行同步複製。一個數據庫表的自增量值,就很可能與另一數據庫相同表的自增量值重複了。當然,這可以通過指定不同的遞增起始值來錯開,但總覺得不爽啊。

      SQL Server中還可以使用timestamp類型的數據來做對象標識符,這可以使對象標識對整個數據庫都是唯一的,而不僅僅是對錶唯一。但其優缺點與表的自增字段一樣。

      2.隨機數字段

      隨機生成對象標識的方法實際就是碰運氣。按照某種複雜的隨機算法迅速產生對象標識,碰一碰對象標識不重複的運氣。只要這種算法產生的對象標識一萬年纔可能重複一次,那你就可以在實際開發中應用這種算法。比如,SQL Server中提供了uniqueidentifier類型,配合NEWID()函數來產生GUID,基本上可以應用於所有主鍵需求了。

      隨機生成標識不需要在系統中維護全局量,不存在自增字段那種加鎖解鎖的性能開銷,對於大量的併發處理來說是個福音。

      同時,即使在分佈式數據庫應用中,不同數據庫產生的隨機值也是不同的。因此,在數據庫的同步複製中,標識相同的就一定是同一條記錄,就不會存在產生主鍵衝突的問題了。

      不過,爲了保證隨機的唯一性,需要大範圍的數值空間。這也使得主鍵字段比較大,在關聯查詢的時候有一定的性能損失,判斷GUID值是否相同總比判斷int值是否相同要多費些功夫。

      在實際應用中到底該用什麼方案來產生對象標識需要根具體情況決定,以上方案僅僅是理論探討。

      接下來要注意的就是主鍵的索引結構的優化了。

      主鍵都要整成聚集索引。聚集索引在SQL Server內部就是聚集表,聚集表是B樹結構,索引值存在B樹的中間節點中,而數據行就存放在B樹的頁節點上。也就是說,聚集索引和數據表其實是一個統一的整體結構。因此,聚集索引查找和定位數據的效率要比一般索引高出很多。

      此外,專門主鍵字段值是永遠都不變。聚集索引建值不變,聚集表的節點也不會調整,硬盤上的數據記錄塊基本不動窩的。這可以極大減少數據庫碎片,除非刪除數據行。數據庫的碎片少了,查詢數據自然要快些。

      一般來說,我們存入數據庫中的數據總是有時間順序的,我們日常查詢和使用的數據也總是近期的數據。有的常用查詢甚至總是按時間順序倒排,比如,郵件列表,論壇帖子。如果主鍵採用的是自增字段,我們不妨將增量值設爲-1或乾脆搞成倒序的主鍵。這樣,查詢的排序與掃描索引的順序相同,畢竟硬盤的磁頭總喜歡從前往後讀,這會稍微提高一點讀取聚集索引的速度。

      同樣,如果使用隨機主鍵值的方案,我們也建議採用與時間相關的隨機數值,而不是GUID。與時間相關的隨機數就是,雖然主鍵是隨機產生的,但後產生的隨機數應該大於先產生的數。

      爲什麼不用GUID呢?因爲它生成的值就像頑皮的猴子,到處亂跳。於是,在每次插入記錄時,聚集索引節點中相鄰的值並不具有時間上的順序。而當我們習慣性地查詢近期數據時,硬盤的磁頭也需要像猴子般亂跳一通之後才能讀到按時間順序的所有數據。

      如果採用有時間順序的隨機值,聚集索引插入數據時總往一個方向增加數據行的頁節點,與同一時期的數據行幾乎總相鄰。查詢同期數據時,磁頭就很少亂跳了。磁頭一次可以讀取一批數據,效率有將有所提升。如果您用電子顯微鏡去觀察硬盤的表面,聚集索引的那顆B樹將生長得很正很齊。

      對磁頭讀寫的優化,是完美主義的程序員所追求的,是否採用完全看自己的心態。總之,高興就好。當然,等將來的大容量存儲設備都用上固態盤了,沒磁頭了,全電信號了,這些優化就都沒有什麼意義了。

      所以呢,追求完美也是很痛苦的。吃了程序員這碗飯,就只能被IT的洪流卷着往前走。我們不過是這洪流中的一粒沙子,不知道又會被帶到何方?

     王菲在一首歌中這樣唱到:一路上有人太早看透生命的線條命運的玄妙,有人太晚覺悟冥冥中該來則來無處可逃...

 

     現在是否對數據庫主鍵設計有了一些認識呢?

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章