轉-數據庫開發經驗談

數據庫設計經驗談(上)

一個成功的管理系統,是由:[50% 的業務 + 50% 的軟件] 所組成,而 50% 的成功軟件又有 [25% 的數據庫 + 25% 的程序] 所組成,數據庫設計的好壞是一個關鍵。如果把企業的數據比做生命所必需的血液,那麼數據庫的設計就是應用中最重要的一部分。有關數據庫設計的材料汗牛充棟,大學學位課程裏也有專門的講述。不過,就如我們反覆強調的那樣,再好的老師也比不過經驗的教誨。所以我歸納歷年來所走的彎路及體會,並在網上找了些對數據庫設計頗有造詣的專業人士給大家傳授一些設計數據庫的技巧和經驗。精選了其中的 60 個最佳技巧,並把這些技巧編寫成了本文,爲了方便索引其內容劃分爲 5 個部分:

第 1 部分 - 設計數據庫之前
這一部分羅列了 12 個基本技巧,包括命名規範和明確業務需求等。
第 2 部分 - 設計數據庫表
總共 24 個指南性技巧,涵蓋表內字段設計以及應該避免的常見問題等。
第 3 部分 - 選擇鍵
怎麼選擇鍵呢?這裏有 10 個技巧專門涉及系統生成的主鍵的正確用法,還有何 時以及如何索引字段以獲得最佳性能等。
第 4 部分 - 保證數據完整性
討論如何保持數據庫的清晰和健壯,如何把有害數據降低到最小程度。
第 5 部分 - 各種小技巧
不包括在以上 4 個部分中的其他技巧,五花八門,有了它們希望你的數據庫開發工作會更輕鬆一些。
第 1 部分 - 設計數據庫之前
考察現有環境
在設計一個新數據庫時,你不但應該仔細研究業務需求而且還要考察現有的系統。大多數數據庫項目都不是從頭開始建立的;通常,機構內總會存在用來滿足特定需求的現有系統(可能沒有實現自動計算)。顯然,現有系統並不完美,否則你就不必再建立新系統了。但是對舊系統的研究可以讓你發現一些可能會忽略的細微問題。一般來說,考察現有系統對你絕對有好處。
定義標準的對象命名規範
一定要定義數據庫對象的命名規範。對數據庫表來說,從項目一開始就要確定表名是採用複數還是單數形式。此外還要給表的別名定義簡單規則(比方說,如果表名是一個單詞,別名就取單詞的前 4 個字母;如果表名是兩個單詞,就各取兩個單詞的前兩個字母組成 4 個字母長的別名;如果表的名字由 3 個單詞組成,你不妨從頭兩個單詞中各取一個然後從最後一個單詞中再取出兩個字母,結果還是組成 4 字母長的別名,其餘依次類推)對工作用表來說,表名可以加上前綴 WORK_ 後面附上採用該表的應用程序的名字。表內的列[字段]要針對鍵採用一整套設計規則。比如,如果鍵是數字類型,你可以用 _N 作爲後綴;如果是字符類型則可以採用 _C 後綴。對列[字段]名應該採用標準的前綴和後綴。再如,假如你的表裏有好多“money”字段,你不妨給每個列[字段]增加一個 _M 後綴。還有,日期列[字段]最好以 D_ 作爲名字打頭。

檢查表名、報表名和查詢名之間的命名規範。你可能會很快就被這些不同的數據庫要素的名稱搞糊塗了。假如你堅持統一地命名這些數據庫的不同組成部分,至少你應該在這些對象名字的開頭用 Table、Query 或者 Report 等前綴加以區別。

如果採用了 Microsoft Access,你可以用 qry、rpt、tbl 和 mod 等符號來標識對象(比如 tbl_Employees)。我在和 SQL Server 打交道的時候還用過 tbl 來索引表,但我用 sp_company (現在用 sp_feft_)標識存儲過程,因爲在有的時候如果我發現了更好的處理辦法往往會保存好幾個拷貝。我在實現 SQL Server 2000 時用 udf_ (或者類似的標記)標識我編寫的函數。
工欲善其事, 必先利其器
採用理想的數據庫設計工具,比如:SyBase 公司的 PowerDesign,她支持 PB、VB、Delphe 等語言,通過 ODBC 可以連接市面上流行的 30 多個數據庫,包括 dBase、FoxPro、VFP、SQL Server 等,今後有機會我將着重介紹 PowerDesign 的使用。
獲取數據模式資源手冊
正在尋求示例模式的人可以閱讀《數據模式資源手冊》一書,該書由 Len Silverston、W. H. Inmon 和 Kent Graziano 編寫,是一本值得擁有的最佳數據建模圖書。該書包括的章節涵蓋多種數據領域,比如人員、機構和工作效能等。其他的你還可以參考:[1]薩師煊 王珊著 數據庫系統概論(第二版)高等教育出版社 1991、[2][美] Steven M.Bobrowski 著 Oracle 7 與客戶/服務器計算技術從入門到精通 劉建元等譯 電子工業出版社,1996、[3]週中元 信息系統建模方法(下) 電子與信息化 1999年第3期,1999
暢想未來,但不可忘了過去的教訓
我發現詢問用戶如何看待未來需求變化非常有用。這樣做可以達到兩個目的:首先,你可以清楚地瞭解應用設計在哪個地方應該更具靈活性以及如何避免性能瓶頸;其次,你知道發生事先沒有確定的需求變更時用戶將和你一樣感到吃驚。

一定要記住過去的經驗教訓!我們開發人員還應該通過分享自己的體會和經驗互相幫助。即使用戶認爲他們再也不需要什麼支持了,我們也應該對他們進行這方面的教育,我們都曾經面臨過這樣的時刻“當初要是這麼做了該多好..”。
在物理實踐之前進行邏輯設計
在深入物理設計之前要先進行邏輯設計。隨着大量的 CASE 工具不斷涌現出來,你的設計也可以達到相當高的邏輯水準,你通常可以從整體上更好地瞭解數據庫設計所需要的方方面面。
瞭解你的業務
在你百分百地確定系統從客戶角度滿足其需求之前不要在你的 ER(實體關係)模式中加入哪怕一個數據表(怎麼,你還沒有模式?那請你參看技巧 9)。瞭解你的企業業務可以在以後的開發階段節約大量的時間。一旦你明確了業務需求,你就可以自己做出許多決策了。

一旦你認爲你已經明確了業務內容,你最好同客戶進行一次系統的交流。採用客戶的術語並且向他們解釋你所想到的和你所聽到的。同時還應該用可能、將會和必須等詞彙表達出系統的關係基數。這樣你就可以讓你的客戶糾正你自己的理解然後做好下一步的 ER 設計。
創建數據字典和 ER 圖表
一定要花點時間創建 ER 圖表和數據字典。其中至少應該包含每個字段的數據類型和在每個表內的主外鍵。創建 ER 圖表和數據字典確實有點費時但對其他開發人員要了解整個設計卻是完全必要的。越早創建越能有助於避免今後面臨的可能混亂,從而可以讓任何瞭解數據庫的人都明確如何從數據庫中獲得數據。

有一份諸如 ER 圖表等最新文檔其重要性如何強調都不過分,這對錶明表之間關係很有用,而數據字典則說明了每個字段的用途以及任何可能存在的別名。對 SQL 表達式的文檔化來說這是完全必要的。
創建模式
一張圖表勝過千言萬語:開發人員不僅要閱讀和實現它,而且還要用它來幫助自己和用戶對話。模式有助於提高協作效能,這樣在先期的數據庫設計中幾乎不可能出現大的問題。模式不必弄的很複雜;甚至可以簡單到手寫在一張紙上就可以了。只是要保證其上的邏輯關係今後能產生效益。
從輸入輸出下手
在定義數據庫表和字段需求(輸入)時,首先應檢查現有的或者已經設計出的報表、查詢和視圖(輸出)以決定爲了支持這些輸出哪些是必要的表和字段。舉個簡單的例子:假如客戶需要一個報表按照郵政編碼排序、分段和求和,你要保證其中包括了單獨的郵政編碼字段而不要把郵政編碼糅進地址字段裏。
報表技巧
要了解用戶通常是如何報告數據的:批處理還是在線提交報表?時間間隔是每天、每週、每月、每個季度還是每年?如果需要的話還可以考慮創建總結表。系統生成的主鍵在報表中很難管理。用戶在具有系統生成主鍵的表內用副鍵進行檢索往往會返回許多重複數據。這樣的檢索性能比較低而且容易引起混亂。
理解客戶需求
看起來這應該是顯而易見的事,但需求就是來自客戶(這裏要從內部和外部客戶的角度考慮)。不要依賴用戶寫下來的需求,真正的需求在客戶的腦袋裏。你要讓客戶解釋其需求,而且隨着開發的繼續,還要經常詢問客戶保證其需求仍然在開發的目的之中。一個不變的真理是:“只有我看見了我才知道我想要的是什麼”必然會導致大量的返工,因爲數據庫沒有達到客戶從來沒有寫下來的需求標準。而更糟的是你對他們需求的解釋只屬於你自己,而且可能是完全錯誤的。
第 2 部分 - 設計表和字段
檢查各種變化
我在設計數據庫的時候會考慮到哪些數據字段將來可能會發生變更。比方說,姓氏就是如此(注意是西方人的姓氏,比如女性結婚後從夫姓等)。所以,在建立系統存儲客戶信息時,我傾向於在單獨的一個數據表裏存儲姓氏字段,而且還附加起始日和終止日等字段,這樣就可以跟蹤這一數據條目的變化。
採用有意義的字段名
有一回我參加開發過一個項目,其中有從其他程序員那裏繼承的程序,那個程序員喜歡用屏幕上顯示數據指示用語命名字段,這也不賴,但不幸的是,她還喜歡用一些奇怪的命名法,其命名採用了匈牙利命名和控制序號的組合形式,比如 cbo1、txt2、txt2_b 等等。
除非你在使用只面向你的縮寫字段名的系統,否則請儘可能地把字段描述的清楚些。當然,也別做過頭了,比如 Customer_Shipping_Address_Street_Line_1,雖然很富有說明性,但沒人願意鍵入這麼長的名字,具體尺度就在你的把握中。
採用前綴命名
如果多個表裏有好多同一類型的字段(比如 FirstName),你不妨用特定表的前綴(比如 CusLastName)來幫助你標識字段。

時效性數據應包括“最近更新日期/時間”字段。時間標記對查找數據問題的原因、按日期重新處理/重載數據和清除舊數據特別有用。
標準化和數據驅動
數據的標準化不僅方便了自己而且也方便了其他人。比方說,假如你的用戶界面要訪問外部數據源(文件、XML 文檔、其他數據庫等),你不妨把相應的連接和路徑信息存儲在用戶界面支持表裏。還有,如果用戶界面執行工作流之類的任務(發送郵件、打印信箋、修改記錄狀態等),那麼產生工作流的數據也可以存放在數據庫裏。預先安排總需要付出努力,但如果這些過程採用數據驅動而非硬編碼的方式,那麼策略變更和維護都會方便得多。事實上,如果過程是數據驅動的,你就可以把相當大的責任推給用戶,由用戶來維護自己的工作流過程。
標準化不能過頭
對那些不熟悉標準化一詞(normalization)的人而言,標準化可以保證表內的字段都是最基礎的要素,而這一措施有助於消除數據庫中的數據冗餘。標準化有好幾種形式,但 Third Normal Form(3NF)通常被認爲在性能、擴展性和數據完整性方面達到了最好平衡。簡單來說,3NF 規定:
* 表內的每一個值都只能被表達一次。
* 表內的每一行都應該被唯一的標識(有唯一鍵)。
* 表內不應該存儲依賴於其他鍵的非鍵信息。
遵守 3NF 標準的數據庫具有以下特點:有一組表專門存放通過鍵連接起來的關聯數據。比方說,某個存放客戶及其有關定單的 3NF 數據庫就可能有兩個表:Customer 和 Order。Order 表不包含定單關聯客戶的任何信息,但表內會存放一個鍵值,該鍵指向 Customer 表裏包含該客戶信息的那一行。
更高層次的標準化也有,但更標準是否就一定更好呢?答案是不一定。事實上,對某些項目來說,甚至就連 3NF 都可能給數據庫引入太高的複雜性。

爲了效率的緣故,對錶不進行標準化有時也是必要的,這樣的例子很多。曾經有個開發餐飲分析軟件的活就是用非標準化表把查詢時間從平均 40 秒降低到了兩秒左右。雖然我不得不這麼做,但我絕不把數據表的非標準化當作當然的設計理念。而具體的操作不過是一種派生。所以如果表出了問題重新產生非標準化的表是完全可能的。
Microsoft Visual FoxPro 報表技巧
如果你正在使用 Microsoft Visual FoxPro,你可以用對用戶友好的字段名來代替編號的名稱:比如用 Customer Name 代替 txtCNaM。這樣,當你用嚮導程序 [Wizards,臺灣人稱爲‘精靈’] 創建表單和報表時,其名字會讓那些不是程序員的人更容易閱讀。
不活躍或者不採用的指示符
增加一個字段表示所在記錄是否在業務中不再活躍挺有用的。不管是客戶、員工還是其他什麼人,這樣做都能有助於再運行查詢的時候過濾活躍或者不活躍狀態。同時還消除了新用戶在採用數據時所面臨的一些問題,比如,某些記錄可能不再爲他們所用,再刪除的時候可以起到一定的防範作用。
使用角色實體定義屬於某類別的列[字段]
在需要對屬於特定類別或者具有特定角色的事物做定義時,可以用角色實體來創建特定的時間關聯關係,從而可以實現自我文檔化。
這裏的含義不是讓 PERSON 實體帶有 Title 字段,而是說,爲什麼不用 PERSON 實體和 PERSON_TYPE 實體來描述人員呢?比方說,當 John Smith, Engineer 提升爲 John Smith, Director 乃至最後爬到 John Smith, CIO 的高位,而所有你要做的不過是改變兩個表 PERSON 和 PERSON_TYPE 之間關係的鍵值,同時增加一個日期/時間字段來知道變化是何時發生的。這樣,你的 PERSON_TYPE 表就包含了所有 PERSON 的可能類型,比如 Associate、Engineer、Director、CIO 或者 CEO 等。
還有個替代辦法就是改變 PERSON 記錄來反映新頭銜的變化,不過這樣一來在時間上無法跟蹤個人所處位置的具體時間。
採用常用實體命名機構數據
組織數據的最簡單辦法就是採用常用名字,比如:PERSON、ORGANIZATION、ADDRESS 和 PHONE 等等。當你把這些常用的一般名字組合起來或者創建特定的相應副實體時,你就得到了自己用的特殊版本。開始的時候採用一般術語的主要原因在於所有的具體用戶都能對抽象事物具體化。
有了這些抽象表示,你就可以在第 2 級標識中採用自己的特殊名稱,比如,PERSON 可能是 Employee、Spouse、Patient、Client、Customer、Vendor 或者 Teacher 等。同樣的,ORGANIZATION 也可能是 MyCompany、MyDepartment、Competitor、Hospital、Warehouse、Government 等。最後 ADDRESS 可以具體爲 Site、Location、Home、Work、Client、Vendor、Corporate 和 FieldOffice 等。
採用一般抽象術語來標識“事物”的類別可以讓你在關聯數據以滿足業務要求方面獲得巨大的靈活性,同時這樣做還可以顯著降低數據存儲所需的冗餘量。
用戶來自世界各地
在設計用到網絡或者具有其他國際特性的數據庫時,一定要記住大多數國家都有不同的字段格式,比如郵政編碼等,有些國家,比如新西蘭就沒有郵政編碼一說。
數據重複需要採用分立的數據表
如果你發現自己在重複輸入數據,請創建新表和新的關係。
每個表中都應該添加的 3 個有用的字段
* dRecordCreationDate,在 VB 下默認是 Now(),而在 SQL Server 下默認爲 GETDATE()
* sRecordCreator,在 SQL Server 下默認爲 NOT NULL DEFAULT USER
* nRecordVersion,記錄的版本標記;有助於準確說明記錄中出現 null 數據或者丟失數據的原因
對地址和電話採用多個字段
描述街道地址就短短一行記錄是不夠的。Address_Line1、Address_Line2 和 Address_Line3 可以提供更大的靈活性。還有,電話號碼和郵件地址最好擁有自己的數據表,其間具有自身的類型和標記類別。

過分標準化可要小心,這樣做可能會導致性能上出現問題。雖然地址和電話表分離通常可以達到最佳狀態,但是如果需要經常訪問這類信息,或許在其父表中存放“首選”信息(比如 Customer 等)更爲妥當些。非標準化和加速訪問之間的妥協是有一定意義的。
使用多個名稱字段
我覺得很吃驚,許多人在數據庫裏就給 name 留一個字段。我覺得只有剛入門的開發人員纔會這麼做,但實際上網上這種做法非常普遍。我建議應該把姓氏和名字當作兩個字段來處理,然後在查詢的時候再把他們組合起來。

我最常用的是在同一表中創建一個計算列[字段],通過它可以自動地連接標準化後的字段,這樣數據變動的時候它也跟着變。不過,這樣做在採用建模軟件時得很機靈才行。總之,採用連接字段的方式可以有效的隔離用戶應用和開發人員界面。
提防大小寫混用的對象名和特殊字符
過去最令我惱火的事情之一就是數據庫裏有大小寫混用的對象名,比如 CustomerData。這一問題從 Access 到 Oracle 數據庫都存在。我不喜歡採用這種大小寫混用的對象命名方法,結果還不得不手工修改名字。想想看,這種數據庫/應用程序能混到採用更強大數據庫的那一天嗎?採用全部大寫而且包含下劃符的名字具有更好的可讀性(CUSTOMER_DATA),絕對不要在對象名的字符之間留空格。
小心保留詞
要保證你的字段名沒有和保留詞、數據庫系統或者常用訪問方法衝突,比如,最近我編寫的一個 ODBC 連接程序裏有個表,其中就用了 DESC 作爲說明字段名。後果可想而知!DESC 是 DESCENDING 縮寫後的保留詞。表裏的一個 SELECT * 語句倒是能用,但我得到的卻是一大堆毫無用處的信息。
保持字段名和類型的一致性
在命名字段併爲其指定數據類型的時候一定要保證一致性。假如字段在某個表中叫做“agreement_number”,你就別在另一個表裏把名字改成“ref1”。假如數據類型在一個表裏是整數,那在另一個表裏可就別變成字符型了。記住,你幹完自己的活了,其他人還要用你的數據庫呢。
仔細選擇數字類型
在 SQL 中使用 smallint 和 tinyint 類型要特別小心,比如,假如你想看看月銷售總額,你的總額字段類型是 smallint,那麼,如果總額超過了 $32,767 你就不能進行計算操作了。
刪除標記
在表中包含一個“刪除標記”字段,這樣就可以把行標記爲刪除。在關係數據庫裏不要單獨刪除某一行;最好採用清除數據程序而且要仔細維護索引整體性。
避免使用觸發器
觸發器的功能通常可以用其他方式實現。在調試程序時觸發器可能成爲干擾。假如你確實需要採用觸發器,你最好集中對它文檔化。
包含版本機制
建議你在數據庫中引入版本控制機制來確定使用中的數據庫的版本。無論如何你都要實現這一要求。時間一長,用戶的需求總是會改變的。最終可能會要求修改數據庫結構。雖然你可以通過檢查新字段或者索引來確定數據庫結構的版本,但我發現把版本信息直接存放到數據庫中不更爲方便嗎?。
給文本字段留足餘量
ID 類型的文本字段,比如客戶 ID 或定單號等等都應該設置得比一般想象更大,因爲時間不長你多半就會因爲要添加額外的字符而難堪不已。比方說,假設你的客戶 ID 爲 10 位數長。那你應該把數據庫表字段的長度設爲 12 或者 13 個字符長。這算浪費空間嗎?是有一點,但也沒你想象的那麼多:一個字段加長 3 個字符在有 1 百萬條記錄,再加上一點索引的情況下才不過讓整個數據庫多佔據 3MB 的空間。但這額外佔據的空間卻無需將來重構整個數據庫就可以實現數據庫規模的增長了。身份證的號碼從 15 位變成 18 位就是最好和最慘痛的例子。
列[字段]命名技巧
我們發現,假如你給每個表的列[字段]名都採用統一的前綴,那麼在編寫 SQL 表達式的時候會得到大大的簡化。這樣做也確實有缺點,比如破壞了自動錶連接工具的作用,後者把公共列[字段]名同某些數據庫聯繫起來,不過就連這些工具有時不也連接錯誤嘛。舉個簡單的例子,假設有兩個表:
Customer 和 Order。Customer 表的前綴是 cu_,所以該表內的子段名如下:cu_name_id、cu_surname、cu_initials 和cu_address 等。Order 表的前綴是 or_,所以子段名是:
or_order_id、or_cust_name_id、or_quantity 和 or_description 等。
這樣從數據庫中選出全部數據的 SQL 語句可以寫成如下所示:
Select * From Customer, Order Where cu_surname = "MYNAME" ;
and cu_name_id = or_cust_name_id and or_quantity = 1
在沒有這些前綴的情況下則寫成這個樣子(用別名來區分):
Select * From Customer, Order Where Customer.surname = "MYNAME" ;
and Customer.name_id = Order.cust_name_id and Order.quantity = 1
第 1 個 SQL 語句沒少鍵入多少字符。但如果查詢涉及到 5 個表乃至更多的列[字段]你就知道這個技巧多有用了。


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