淺談數據庫設計技巧(上)(轉)

說到數據庫, 我認爲不能不先談數據結構。1996年,在我初入大學學習計算機編程時,當時的老師就告訴咱們說:計算機程序=數據結構+算法。儘管如今的程序開發已由面 向過程爲主逐步過渡到面向對象爲主,但我仍是深深贊同8年前老師的告訴咱們的公式:計算機程序=數據結構+算法。面向對象的程序開發,要作的第一件事就 是,先分析整個程序中需處理的數據,從中提取出抽象模板,以這個抽象模板設計類,再在其中逐步添加處理其數據的函數(即算法),最後,再給類中的數據成員和函數劃分訪問權限,從而實現封裝。

  數據庫的最初雛形聽說源自美國一個奶牛場的記帳薄(紙質的,因而可知,數據庫並不必定是存儲在電腦裏的數據^_^),裏面記錄的是該奶牛場的收 支帳目,程序員在將其整理、錄入到電腦中時從中受到啓發。當按照規定好的數據結構所採集到的數據量大到必定程度後,出於程序執行效率的考慮,程序員將其中 的檢索、更新維護等功能分離出來,作成單獨調用的模塊,這個模塊後來就慢慢發展、演變成如今咱們所接觸到的數據庫管理系統(DBMS)——程序開發中的一 個重要分支。   下面進入正題,首先按我我的所接觸過的程序給數據庫設計人員的功底分一下類:   1、沒有系統學習過數據結構的程序員。這類程序員的做 品每每只是他們的即興玩具,他們每每習慣只設計有限的幾個表,實現某類功能的數據所有塞在一個表中,各表之間幾乎毫無關聯。網上很多的免費管理軟件都是這 樣的東西,當程序功能有限,數據量很少的時候,其程序運行起來沒有什麼問題,可是若是用其管理比較重要的數據,風險性很是大。   2、系統學習過數據結構,可是尚未開發過對程序效率要求比較高的管理軟件的程序員。這類人多半剛從學校畢業不久,他們在設計數據庫表結構時,嚴格按照教科書上的規定,死扣E-R圖和3NF(別灰心,全部的數據庫設計高手都是從這一步開始的)。他們的做品,對於通常的access型輕量級的管理軟件,已經夠用。可是一旦該系統須要添加新功能,原有的數據庫表差很少得進行大換血。    3、第二類程序員,在經歷過數次程序效率的提高,以及功能升級的折騰後,終於升級成爲數據庫設計的老鳥,第一類程序員眼中的高人。這類程序員能夠勝任二 十個表以上的中型商業數據管理系統的開發工做。他們知道該在什麼樣的狀況下保留必定的冗餘數據來提升程序效率,並且其設計的數據庫可拓展性較好,當用戶需 要添加新功能時,原有數據庫表只需作少許修改便可。   4、在經歷過上十個相似數據庫管理軟件的重複設計後,第三類程序員中堅持下來沒有轉行,而 是但願從中找出「偷懶」竅門的有心人會慢慢覺悟,從而完成量變到質變的轉換。他們所設計的數據庫表結構有必定的遠見,可以預測到將來功能升級所須要的數 據,從而預先留下伏筆。這類程序員目前大多晉級成數據挖掘方面的高級軟件開發人員。   5、第三類程序員或第四類程序員,在對現有的各家數據庫管理系統的原理和開發都有必定的鑽研後,要麼在其基礎上進行二次開發,要麼自行開發一套有自主版權的通用數據庫管理系統。   我我的正處於第三類的末期,因此下面所列出的一些設計技巧只適合第二類和部分第三類數據庫設計人員。同時,因爲我不多碰到有興趣在這方面深鑽下去的同行,因此文中不免出現錯誤和遺漏,在此先行聲明,歡迎你們指正,不要藏私哦8)   1、樹型關係的數據表   很多程序員在進行數據庫設計的時候都遇到過樹型關係的數據,例如常見的類別表,即一個大類,下面有若干個子 類,某些子類又有子類這樣的狀況。當類別不肯定,用戶但願能夠在任意類別下添加新的子類,或者刪除某個類別和其下的全部子類,並且預計之後其數量會逐步增 長,此時咱們就會考慮用一個數據表來保存這些數據。按照教科書上的教導,第二類程序員大概會設計出相似這樣的數據表結構: 類別表_1(Type_table_1) 名稱     類型    約束條件   說明 type_id   int   無重複   類別標識,主鍵 type_name  char(50) 不容許爲空 類型名稱,不容許重複 type_father int 不容許爲空 該類別的父類別標識,若是是頂節點的話設定爲某個惟一值   這樣的設計短小精悍,徹底知足3NF,並且能夠知足用戶的全部要求。是否是這樣就行呢?答案是NO!Why?   咱們來估計一下用戶但願如何羅列出這個表的數據的。對用戶而言,他固然指望按他所設定的層次關係一次羅列出全部的類別,例如這樣: 總類別   類別1     類別1.1       類別1.1.1     類別1.2   類別2     類別2.1   類別3     類別3.1     類別3.2   ……   看看爲了實現這樣的列表顯示(樹的先序遍歷),要對上面的表進行多少次檢索?注意,儘管類別1.1.1多是在類別3.2以後添加的記錄,答案 仍然是N次。這樣的效率對於少許的數據沒什麼影響,可是往後類型擴充到數十條甚至上百條記錄後,單單列一次類型就要檢索數十次該表,整個程序的運行效率就 不敢恭維了。或許第二類程序員會說,那我再建一個臨時數組或臨時表,專門保存類型表的先序遍歷結果,這樣只在第一次運行時檢索數十次,再次羅列全部的類型 關係時就直接讀那個臨時數組或臨時表就好了。其實,用不着再去分配一塊新的內存來保存這些數據,只要對數據表進行必定的擴充,再對添加類型的數量進行一下 約束就好了,要完成上面的列表只需一次檢索就好了。下面是擴充後的數據表結構: 類別表_2(Type_table_2) 名稱     類型    約束條件    說明 type_id   int   無重複   類別標識,主鍵 type_name  char(50) 不容許爲空 類型名稱,不容許重複 type_father int 不容許爲空 該類別的父類別標識,若是是頂節點的話設定爲某個惟一值 type_layer char(6) 限定3層,初始值爲000000 類別的先序遍歷,主要爲減小檢索數據庫的次數   按照這樣的表結構,咱們來看看上面例子記錄在表中的數據是怎樣的: type_id type_name type_father type_layer 1 總類別 0 000000 2 類別1 1 010000 3 類別1.1 2 010100 4 類別1.2 2 010200 5 類別2 1 020000 6 類別2.1 5 020100 7 類別3 1 030000 8 類別3.1 7 030100 9 類別3.2 7 030200 10 類別1.1.1 3 010101 ……   如今按type_layer的大小來檢索一下:SELECT * FROM Type_table_2 ORDER BY type_layer 列出記錄集以下: type_id type_name type_father type_layer 1 總類別 0 000000 2 類別1 1 010000 3 類別1.1 2 010100 10 類別1.1.1 3 010101 4 類別1.2 2 010200 5 類別2 1 020000 6 類別2.1 5 020100 7 類別3 1 030000 8 類別3.1 7 030100 9 類別3.2 7 030200 ……   如今列出的記錄順序正好是先序遍歷的結果。在控制顯示類別的層次時,只要對type_layer字段中的數值進行判斷,每2位一組,如大於0則 向右移2個空格。固然,我這個例子中設定的限制條件是最多3層,每層最多可設99個子類別,只要按用戶的需求狀況修改一下type_layer的長度和位 數,便可更改限制層數和子類別數。其實,上面的設計不僅僅只在類別表中用到,網上某些可按樹型列表顯示的論壇程序大多采用相似的設計。   或許有人認爲,Type_table_2中的type_father字段是冗餘數據,能夠除去。若是這樣,在插入、刪除某個類別的時候,就得對 type_layer 的內容進行比較繁瑣的斷定,因此我並無消去type_father字段,這也正符合數據庫設計中適當保留冗餘數據的來下降程序複雜度的原則,後面我會舉 一個故意增長數據冗餘的案例。      2、商品信息表的設計   假設你是一家百貨公司電腦部的開發人員,某天老闆要求你爲公司開發一套網上電子商務平臺,該百貨 公司有數千種商品出售,不過目前僅打算先在網上銷售數十種方便運輸的商品,固然,之後可能會陸續在該電子商務平臺上增長新的商品出售。如今開始進行該平臺 數據庫的商品信息表的設計。每種出售的商品都會有相同的屬性,如商品編號,商品名稱,商品所屬類別,相關信息,供貨廠商,內含件數,庫存,進貨價,銷售 價,優惠價。你很快就設計出4個表:商品類型表(Wares_type),供貨廠商表(Wares_PRovider),商品信息表(Wares_info): 商品類型表(Wares_type) 名稱     類型    約束條件    說明 type_id   int   無重複   類別標識,主鍵 type_name   char(50) 不容許爲空 類型名稱,不容許重複 type_father int 不容許爲空 該類別的父類別標識,若是是頂節點的話設定爲某個惟一值 type_layer char(6) 限定3層,初始值爲000000 類別的先序遍歷,主要爲減小檢索數據庫的次數 供貨廠商表(Wares_provider) 名稱      類型    約束條件    說明 provider_id int   無重複   供貨商標識,主鍵 provider_name char(100) 不容許爲空 供貨商名稱 商品信息表(Wares_info) 名稱     類型    約束條件    說明 wares_id int   無重複   商品標識,主鍵 wares_name char(100) 不容許爲空 商品名稱 wares_type   int 不容許爲空     商品類型標識,和Wares_type.type_id關聯 wares_info char(200) 容許爲空 相關信息 provider int 不容許爲空 供貨廠商標識,和Wares_provider.provider_id關聯 setnum int 初始值爲1 內含件數,默認爲1 stock int 初始值爲0 庫存,默認爲0 buy_price money 不容許爲空 進貨價 sell_price money 不容許爲空 銷售價 discount money 不容許爲空 優惠價   你拿着這3個表給老闆檢查,老闆但願可以再添加一個商品圖片的字段,不過只有一部分商品有圖片。OK,你在商品信息表(Wares_info)中增長了一個haspic的BOOL型字段,而後再建了一個新表——商品圖片表(Wares_pic): 商品圖片表(Wares_pic) 名稱     類型    約束條件    說明 pic_id int   無重複   商品圖片標識,主鍵 wares_id int 不容許爲空 所屬商品標識,和Wares_info.wares_id關聯 pic_address  char(200) 不容許爲空       圖片存放路徑   程序開發完成後,徹底知足老闆目前的要求,因而正式啓用。一段時間後,老闆打算在這套平臺上推出新的商品銷售,其中,某類商品所有都需添加「長 度」的屬性。第一輪折騰來了……固然,你按照添加商品圖片表的老方法,在商品信息表(Wares_info)中增長了一個haslength的BOOL型 字段,又建了一個新表——商品長度表(Wares_length): 商品長度表(Wares_length) 名稱     類型    約束條件    說明 length_id int   無重複   商品圖片標識,主鍵 wares_id int 不容許爲空 所屬商品標識,和Wares_info.wares_id關聯 length  char(20) 不容許爲空     商品長度說明   剛剛改完沒多久,老闆又打算上一批新的商品,此次某類商品所有須要添加「寬度」的屬性。你咬了咬牙,又照方抓藥,添加了商品寬度表 (Wares_width)。又過了一段時間,老闆新上的商品中有一些須要添加「高度」的屬性,你是否是開始以爲你所設計的數據庫按照這種方式增加下去, 很快就能變成一個迷宮呢?那麼,有沒有什麼辦法遏制這種不可預見性,但卻相似重複的數據庫膨脹呢?我在閱讀《敏捷軟件開發:原則、模式與實踐》中發現做者 舉過相似的例子:7.3 「Copy」程序。其中,我很是贊同敏捷軟件開發這個觀點:在最初幾乎不進行預先設計,可是一旦需求發生變化,此時做爲一名追求 卓越的程序員,應該從頭審查整個架構設計,在這次修改中設計出可以知足往後相似修改的系統架構。下面是我在須要添加「長度」的屬性時所提供的修改方案:   去掉商品信息表(Wares_info)中的haspic字段,添加商品額外屬性表(Wares_ex_property)和商品額外信息表(Wares_ex_info)2個表來完成添加新屬性的功能。 商品額外屬性表(Wares_ex_property) 名稱     類型    約束條件    說明 ex_pid int   無重複   商品額外屬性標識,主鍵 p_name char(20) 不容許爲空 額外屬性名稱 商品額外信息表(Wares_ex_info) 名稱     類型    約束條件    說明 ex_iid int   無重複   商品額外信息標識,主鍵 wares_id int 不容許爲空 所屬商品標識,和Wares_info.wares_id關聯 property_id  int 不容許爲空     商品額外屬性標識,和Wares_ex_property.ex_pid關聯 property_value char(200) 不容許爲空 商品額外屬性值   在商品額外屬性表(Wares_ex_property)中添加2條記錄: ex_pid p_name 1 商品圖片 2 商品長度   再在整個電子商務平臺的後臺管理功能中追加一項商品額外屬性管理的功能,之後添加新的商品時出現新的屬性,只需利用該功能往商品額外屬性表 (Wares_ex_property)中添加一條記錄便可。不要懼怕變化,被第一顆子彈擊中並非壞事,壞的是被相同軌道飛來的第二顆、第三顆子彈擊 中。第一顆子彈來得越早,所受的傷越重,以後的抵抗力也越強8)(待續)程序員

相關文章
相關標籤/搜索