|
1969年8月8日,在北京協(xié)和醫(yī)院降生了一個漂亮的小女孩。接生的阿姨說,她的聲音這么大,好象想要全世界的人都聽到。
后來,她的父親為她取了一個很好聽的名字,叫“王菲”。于是,所有的小朋友就叫她“王菲”,“王菲”就是她童年的主鍵。
在她上初二的時候,認識了二班另一個叫“王菲”的同學(xué),而且和她同一天生日。不過,同學(xué)們常常將她倆弄錯,后來,就分別叫她們“大王菲”和“小王菲”。“大王菲”就是她在同學(xué)們心目中的主鍵。
在大王菲18歲的那一年,她領(lǐng)到了她的身份證。從此,她有了新的身份標識“100321690808022”,這一標識可以唯一區(qū)別中國大陸的每一個人。同時,原來二班的“小王菲”也領(lǐng)到了她自己的身份證,“100321690808006”。于是,人們就可以用身份證號,唯一標識兩個“王菲”了。身份證號就是她成年后的主鍵。
由于她的歌唱的非常好,沒多久就成了歌星。后來她去了香港發(fā)展,娛樂公司非要將她的名字更改為“王靖雯”,雖然她并不喜歡這個名字,但為了事業(yè)的發(fā)展還是同意了。“王靖雯”就成了她在娛樂圈里的主鍵。
“王靖雯”的歌打動了許許多多的歌迷,很快就成了歌壇天后。歌迷們將“王靖雯”這一主鍵與無數(shù)條動聽的歌曲記錄關(guān)聯(lián)在了一起,并形成了一個龐大的歌迷數(shù)據(jù)庫。
沒多久,王靖雯和一個彈電吉他的小子相愛了。那小子說,還是“王菲”這個名字好聽,于是,“王靖雯”又變回“王菲”了。主鍵被那小子修改了,這下麻煩大了。歌迷們都糊涂了,是將她的歌關(guān)聯(lián)到“王菲”還是“王靖雯”呢?這一主鍵的修改,導(dǎo)致歌迷數(shù)據(jù)庫的一次大混亂。
再后來呢,她和那個彈電吉他的小子結(jié)婚了,香港政府將他們的身份證號碼,用結(jié)婚證書關(guān)聯(lián)起來。這本也算一樁好事,可是,月老在酒醒之后發(fā)現(xiàn)了這一錯誤,就將關(guān)聯(lián)的記錄刪除了。陰差陽錯,命運使然,她和那個彈電吉他的小子只好分手了。
但是,正如接生的阿姨說的那樣,她的聲音的確讓全世界的人都聽到了!
講完這個故事之后,我們是否能領(lǐng)悟到數(shù)據(jù)庫設(shè)計的一些哲理呢?
什么是主鍵?
關(guān)系數(shù)據(jù)庫說,為了唯一區(qū)分表的每一行記錄,必須為表確定一個主鍵。主鍵可以是一個或多個列組成,這些主鍵列的值不能重復(fù)。主鍵是兩個表進行關(guān)聯(lián)的基礎(chǔ),所謂“關(guān)系”體現(xiàn)的是一個表的字段與另一個表的主鍵的關(guān)聯(lián)。
在日常的應(yīng)用項目開發(fā)中,我們常常都會面臨“該拿什么字段來做主鍵”的難題。
用名字做主鍵?一班的“王菲”和二班的“王菲”可能重復(fù)。
用身份證號做主鍵?在大陸也許不會重復(fù),去香港后,人家卻不認。本不相信第二代身份證號會重復(fù),但俺的一位同事正好撞上!
同樣,主鍵值的更新也是麻煩事兒。“王靖雯”變成“王菲”那段日子,貨架上一張唱片是王靖雯的,另一張又是王菲的,許多歌迷還以為又有新人出現(xiàn)了。
請不要說可以級聯(lián)更新,將你那個做主鍵的字段加長兩位試試?想當年,當身份證號從15位增加到18位時,給銀行、證券、政府、企業(yè)...等多條戰(zhàn)線上的兄弟們制造了怎樣的痛苦啊!
為什么會出現(xiàn)這些問題呢?
原因就是:錯誤地把對象屬性當作對象標識!
對象屬性和對象標識都是對象數(shù)據(jù)庫中的概念。
什么是對象屬性呢?
就是業(yè)務(wù)邏輯上涉及的任何可變信息,什么“姓名”、“性別”、“身份證號”、“訂單號”...通通都是對象屬性。對象屬性總會變化的,只是有些變得快,有些變得慢而已。
對象標識是啥?就是唯一區(qū)分數(shù)據(jù)對象的鑒別符,對象標識存在的唯一目的就是區(qū)分對象,除此之外沒有任何業(yè)務(wù)邏輯上的意義。
不管王菲的屬性值怎樣變化,但王菲還是王菲,不是二班的那個“王菲”。也就是說,王菲的靈魂未變,她是不會改變的,就象哲學(xué)上所說的“不以人的意志為轉(zhuǎn)移”。這種唯一表示對象本身的東西,就是對象標識!
對象標識是唯一的。也就是說,即使兩個對象,他們的屬性完全一樣,但它們的對象標識是不同的。畢竟,同名同姓甚至同一天出生的大王菲和小王菲是兩個不同的人。
對象的標識是永恒不變的。一旦對象產(chǎn)生,它的標識就自然地、唯一地產(chǎn)生了。盡管王菲換了名,身份證號也變過,但王菲的對象標識未變。即使到了下個世紀,她的對象標識也將依然存在于歌迷們的們的心中。
對象的標識是描述關(guān)系的基礎(chǔ)。王菲唱的歌是王菲唱的,不是初二二班的那個“王菲”唱的。王靖雯唱的歌就是王菲唱的歌,有的歌迷只將歌曲和歌手的人名關(guān)聯(lián)起來,難怪會出混亂。香港政府也犯相同的錯誤,將王菲的身份證號碼這一內(nèi)部屬性,跟那個彈電吉他的小子關(guān)聯(lián)起來,也許就是命運的錯誤。
那么,我們在設(shè)計數(shù)據(jù)庫結(jié)構(gòu)時,到底該用什么來做主鍵呢?
對象數(shù)據(jù)庫說,主鍵只能是對象標識!
至于對象屬性是否唯一,那是由業(yè)務(wù)邏輯所決定的。如果業(yè)務(wù)邏輯規(guī)定訂單號不能重復(fù),就為訂單號建一個唯一索引好了,但它不是主鍵。
所以,當我們看到有些數(shù)據(jù)庫設(shè)計采用了額外的一個字段來專門充當主鍵,并用這個主鍵與其他表關(guān)聯(lián)的話,那就是已經(jīng)走到對象數(shù)據(jù)庫的門口了。什么“內(nèi)部碼”,“流水號”,“序列號”,“自增數(shù)”...通通都可以算是對象標識。
為什么SQL Server要提供自增量字段以及GUID的標識字段呢?都是為了這專門的主鍵字段服務(wù)的。
如果我們打算向?qū)ο髷?shù)據(jù)庫路上走得話,就請使用對象標識來做專門的主鍵字段吧。
當然,怎樣產(chǎn)生唯一的對象標識來做主鍵,這也是有說道的。
1.用自增量字段
自增量字段每次都會按順序遞增,可以保證在一個表里的主鍵不重復(fù)。除非超出了自增字段類型的最大值并從頭遞增,但這幾乎不可能。使用自增量字段來做主鍵是非常簡單的,一般只需在建表時聲明自增屬性即可。
自增量字段的長度可以很短,比如使用一個int類型就基本上夠用了。簡短的主鍵可以在大量數(shù)據(jù)和復(fù)雜的關(guān)系查詢中表現(xiàn)出更好的性能。
自增量的值都是需要在系統(tǒng)中維護一個全局的數(shù)據(jù)值,每次插入數(shù)據(jù)時即對此次值進行增量取值。當在當量產(chǎn)生唯一標識的并發(fā)環(huán)境中,每次的增量取值都必須最此全局值加鎖解鎖以保證增量的唯一性。這可能是一個并發(fā)的瓶頸,會牽扯一些性能問題。
還有,如果要搞分布式數(shù)據(jù)庫的話,這自增量字段就有問題了。因為,在分布式數(shù)據(jù)庫中,不同數(shù)據(jù)庫的同名的表可能需要進行同步復(fù)制。一個數(shù)據(jù)庫表的自增量值,就很可能與另一數(shù)據(jù)庫相同表的自增量值重復(fù)了。當然,這可以通過指定不同的遞增起始值來錯開,但總覺得不爽啊。
SQL Server中還可以使用timestamp類型的數(shù)據(jù)來做對象標識符,這可以使對象標識對整個數(shù)據(jù)庫都是唯一的,而不僅僅是對表唯一。但其優(yōu)缺點與表的自增字段一樣。
2.隨機數(shù)字段
隨機生成對象標識的方法實際就是碰運氣。按照某種復(fù)雜的隨機算法迅速產(chǎn)生對象標識,碰一碰對象標識不重復(fù)的運氣。只要這種算法產(chǎn)生的對象標識一萬年才可能重復(fù)一次,那你就可以在實際開發(fā)中應(yīng)用這種算法。比如,SQL Server中提供了uniqueidentifier類型,配合NEWID()函數(shù)來產(chǎn)生GUID,基本上可以應(yīng)用于所有主鍵需求了。
隨機生成標識不需要在系統(tǒng)中維護全局量,不存在自增字段那種加鎖解鎖的性能開銷,對于大量的并發(fā)處理來說是個福音。
同時,即使在分布式數(shù)據(jù)庫應(yīng)用中,不同數(shù)據(jù)庫產(chǎn)生的隨機值也是不同的。因此,在數(shù)據(jù)庫的同步復(fù)制中,標識相同的就一定是同一條記錄,就不會存在產(chǎn)生主鍵沖突的問題了。
不過,為了保證隨機的唯一性,需要大范圍的數(shù)值空間。這也使得主鍵字段比較大,在關(guān)聯(lián)查詢的時候有一定的性能損失,判斷GUID值是否相同總比判斷int值是否相同要多費些功夫。
在實際應(yīng)用中到底該用什么方案來產(chǎn)生對象標識需要根具體情況決定,以上方案僅僅是理論探討。
接下來要注意的就是主鍵的索引結(jié)構(gòu)的優(yōu)化了。
主鍵都要整成聚集索引。聚集索引在SQL Server內(nèi)部就是聚集表,聚集表是B樹結(jié)構(gòu),索引值存在B樹的中間節(jié)點中,而數(shù)據(jù)行就存放在B樹的頁節(jié)點上。也就是說,聚集索引和數(shù)據(jù)表其實是一個統(tǒng)一的整體結(jié)構(gòu)。因此,聚集索引查找和定位數(shù)據(jù)的效率要比一般索引高出很多。
此外,專門主鍵字段值是永遠都不變。聚集索引建值不變,聚集表的節(jié)點也不會調(diào)整,硬盤上的數(shù)據(jù)記錄塊基本不動窩的。這可以極大減少數(shù)據(jù)庫碎片,除非刪除數(shù)據(jù)行。數(shù)據(jù)庫的碎片少了,查詢數(shù)據(jù)自然要快些。
一般來說,我們存入數(shù)據(jù)庫中的數(shù)據(jù)總是有時間順序的,我們?nèi)粘2樵兒褪褂玫臄?shù)據(jù)也總是近期的數(shù)據(jù)。有的常用查詢甚至總是按時間順序倒排,比如,郵件列表,論壇帖子。如果主鍵采用的是自增字段,我們不妨將增量值設(shè)為-1或干脆搞成倒序的主鍵。這樣,查詢的排序與掃描索引的順序相同,畢竟硬盤的磁頭總喜歡從前往后讀,這會稍微提高一點讀取聚集索引的速度。
同樣,如果使用隨機主鍵值的方案,我們也建議采用與時間相關(guān)的隨機數(shù)值,而不是GUID。與時間相關(guān)的隨機數(shù)就是,雖然主鍵是隨機產(chǎn)生的,但后產(chǎn)生的隨機數(shù)應(yīng)該大于先產(chǎn)生的數(shù)。
為什么不用GUID呢?因為它生成的值就像頑皮的猴子,到處亂跳。于是,在每次插入記錄時,聚集索引節(jié)點中相鄰的值并不具有時間上的順序。而當我們習(xí)慣性地查詢近期數(shù)據(jù)時,硬盤的磁頭也需要像猴子般亂跳一通之后才能讀到按時間順序的所有數(shù)據(jù)。
如果采用有時間順序的隨機值,聚集索引插入數(shù)據(jù)時總往一個方向增加數(shù)據(jù)行的頁節(jié)點,與同一時期的數(shù)據(jù)行幾乎總相鄰。查詢同期數(shù)據(jù)時,磁頭就很少亂跳了。磁頭一次可以讀取一批數(shù)據(jù),效率有將有所提升。如果您用電子顯微鏡去觀察硬盤的表面,聚集索引的那顆B樹將生長得很正很齊。
對磁頭讀寫的優(yōu)化,是完美主義的程序員所追求的,是否采用完全看自己的心態(tài)。總之,高興就好。當然,等將來的大容量存儲設(shè)備都用上固態(tài)盤了,沒磁頭了,全電信號了,這些優(yōu)化就都沒有什么意義了。
所以呢,追求完美也是很痛苦的。吃了程序員這碗飯,就只能被IT的洪流卷著往前走。我們不過是這洪流中的一粒沙子,不知道又會被帶到何方?
王菲在一首歌中這樣唱到:一路上有人太早看透生命的線條命運的玄妙,有人太晚覺悟冥冥中該來則來無處可逃...
原創(chuàng)作者:李戰(zhàn)(leadzen).深圳 2008-5-10
原文地址:http://www.cnblogs.com/leadzen/archive/2008/05/10/1191010.html
【轉(zhuǎn)載請注明作者及出處】
鄭重聲明:本文版權(quán)歸原作者所有,轉(zhuǎn)載文章僅為傳播更多信息之目的,如作者信息標記有誤,請第一時間聯(lián)系我們修改或刪除,多謝。