數據庫設計漫談


 引言

  數據庫設計規范,仁者見仁,但是有共同的目標都是想要更加簡潔清新,可維護可擴展等等。有時候設計的時候沒有想到,等到開發的時候,或者發布完了,客戶幫我們發現BUG,那是很得不償失的事,這些得不償失的事,我都經歷着或經歷過,記得剛畢業出來工作,對命名沒有什么概念,很隨意,更別談用心去設計了,后面帶來的痛只有自己清楚。所以對細節和規范,我覺得特別有感同身受,如果看到某個人的博客,能把數據庫的設計多點分享,那是很感激的,自己的想法也有些,但是終究還是沒有那么的系統,權作漫談,或許能有感同身受,那也是一份貢獻。

 1.關於主表和從表的命名

    有時候在尋找bug的過程中,會關聯主從表進行定位排錯。主從表命名的好,查庫很方便,很容易就可以定位到錯誤。這里舉個項目中的實例。

    這是我同事設計的表:(因為用到Oracle,所以采用大寫命名)

      表1:MES_BASIC_PRODUCT_OQC(主表-產品出庫檢基礎資料表)

      表2: MES_BAS_OQC_ITEMS(從表-出庫檢項目明細基礎資料表)

      表3:MES_OQC_CHECK (主表-出庫檢驗數據采集表)

      表4:MES_OQC_CHECK_ITEMS(從表-出庫檢驗項目明細數據采集表

      其中主表表1,表3 表有一個共同的字段:PRODUCTID

        先談一談自己的想法。

      第一,如果從美觀和一致的原則,MES_BASIC_PRODUCT_OQ這個名稱應該命名為MES_BAS_PRODUCT_OQC,這樣做可能並不起眼,但是養成一種好的習慣,其實更難能可貴。因為編碼本身是一種細活,碰到因為細節導致的災難太多了。所以要把錯誤扼殺在萌芽階段,把一個簡單的功能做到能力的極致,我覺得是一個優秀程序員的品德。至於表3和表4命名,遵循了繼承的方式,清爽可讀。

      第二,接下來說說不遵循這種清爽可讀帶來的麻煩。比如有個這樣的問題,我們查詢表2(MES_BAS_OQC_ITEMS)和表4(MES_OQC_CHECK_ITEMS)各自的項目編號字段(ITEM_NO)看看是否一致,但是表3是'03-01',表4是'04-01',二者應該是一致的,這里出現了不同。於是我們要追溯各自的主表,尋找主表的產品編號PRODUCTID是否一致。於是我們從庫里自然而然的開始select *from 主表。這里主表名稱叫什么?有什么辦法可以根據從表名稱直接推斷出主表的名稱?顯然表1和表2的住從表命名給我們帶來了麻煩,因為我們無法從從表推斷出主表的名稱。於是我們要么查閱ER圖,要么SQL里去從100多張表里面去定位主表,很麻煩。

      匯總:主從表盡可能遵循繼承關系,這里的繼承指的是名稱上的父子關系的直觀顯示。比如表1和表2,可以這樣設計成MES_BAS_PRODUCT_OQC和MES_BAS_PRODUCT_OQC_DETAIL表3和表4可以設計成MES_OQC_CHECK和MES_OQC_CHECK_DETAIL。這樣在以后的定位和尋找就事半功倍,沒有什么技術含量,確是很好的習慣。

 2.關於基礎表的設計

    我在項目發布后一段時間,客戶給我匯總了bug文檔,其中有一個問題是這樣的,如果下面兩張圖所示:

    上表-對應數據庫的基礎表:

  

  下表-對應數據庫的主表:

  

  最后客戶在旁邊注釋到:檢驗標准中的公差和檢驗記錄信息的公差 值顯示有出入

  我查了下數據顯示不一致原因是是這樣的,主表從基礎表帶過來數據后,客戶把基礎表的標准,上偏差,下偏差部分修改掉了。這樣客戶匹配的時候,就出現了同一個產品,檢驗標准,上下偏差主從表不一致的情況。

   如果基礎表是個小基礎資料表,可以用版本很容易的控制這種情況的發生,但是基礎表是個大基礎表,客戶都是用Excel大批量的進行導入操作的。所以用版本來做,同一個產品有可能出現N個版本,這個表就變得非常龐大,不是很好辦。想到的第二種做法是,標准和上偏差,下偏差只存在於基礎表當中,主表使用這幾個字段。這樣主表做顯示的時候,直接管理基礎表關聯數據。這樣可以保證數據的一致性。但是問題又來了,客戶可能對基礎表進行后期的增刪改操作,這樣就造成,引用他主表的數據關聯不到歷史數據。想來想去,還是在主表當中保留標准,上偏差,下偏差三個字段,用於存儲從基礎表帶過來的對應數據,這樣保證了歷史記錄的完整性。這樣,客戶如果修改基礎表,那么主表只能引用到最新修改的數據,至於基礎表被改后,主表的記錄對應的顯示只能是歷史記錄,這樣其實可以用來做追溯用。就這樣設計吧。暫時想不到完美的辦法。

  

 3.以非空的思想設計字段

  客戶又反饋一個問題:進料檢驗記錄有記錄,進料檢驗日報確沒有記錄。如下所示

  進料檢驗記錄:

  

  進料檢驗日報:

  

  這兩個信息都是來自同一張進料檢驗表,但是為什么確會出現不同的記錄?

  我們先看一下數據庫進料檢驗表的設計:如下圖 

  

  我們注意到Nulls這一列,我們來分析問題背后,經常被忽視的設計理念。我們觀察發現這張表大部分字段都可空(NULL),當前所在系統,很多表的字段都是盡可能的可空,這張表只是一個代表。可空的好處是放得很寬,這樣很符合企業復雜的業務需要,同時編碼的時候也可以少些約束和驗證。但是不經意的放寬的代價就是后面關聯查詢做報表顯示的時候,會經常出現一些莫名其妙的錯誤。

  上面問題的產生,根源在於部門編號(DEPTID)這個字段設計成可空。部門編號使用的目的是用來區分不同部門對數據的操作權限。比如圖2中,進料檢驗日報上面的部門數據選擇這個查詢條件,可以篩選不同部門的數據信息。那么這個部門編號到底能否為空?按照設計為可空,那么數據采集后的結果可能是這樣的:

  

  如此,那么上面的進料檢驗日報圖,如何可查詢到結果呢?那么,在部門數據選擇這個查詢條件加一個"全部"查詢條件來查詢DEPTID為NULL的數據何如?也許可以打補丁式的修復這個問題,但是總感覺特別怪,簡單的東西變得復雜化了。那該怎么處理?我覺得還是要回到這個問題:這個部門編號到底能否為空?

從業務上來看,除了基礎表可能涉及到可空,其他表其實都應該是不可空,如果可空,那么這條空記錄如何追溯?基礎表如果用NULL表示該記錄屬於所有部門,其實也不算很好的做法,存NULL記錄還不如直接存儲部門里面的根節點,比如項目所屬公司的編號。這樣整個結構看起來就更加清晰明了了。

 4、關於命名的分類

  如果數據庫的表多了,沒有做一個分類,想要快速定位某個模塊的某個表其實是很吃力的。為什么這么理解的,先看圖示如下:

  

    

    

    

  我們先觀察一下這個系統的表命名方式。通用級別的表采用的是BAS前綴或SEC前綴,系統級別的用的是MES_BAS或MES_IQC前綴。如果不適用這些前綴會怎樣呢?比如我們要定位某個模塊的某張表,我們找尋起來就特別費勁。當然因為加上了分類的前綴,表明可能會變長,如果超過30個字符,Oracle就不支持。不過這不是問題,問題是我們不能沒有分類,特別是一個大的系統。

  至於字段的命名,要不要分類呢?這個和表明是否要保持一致?比如User表是用UserID好還是ID好。先看一個我同事設計的表

  

  注意到這里使用了USER前綴,其實是多余的,User表為什么還要加User前綴呢?其次,我們寫SQL帶來工作量的多余,select sec_user.username,sec_user.userId……。如果表格字段很多,那就真的冗余了。當然如果表不多,字段有限,那就另當別論了。但是如果考慮到可擴展,為什么不把簡單的東西做到最好呢?

  

 5、關於分類表的設計

  客戶,供應商,訂單等等都有自己的分類,包括博客園里面的文章分類什么的,經常要設計這么一個分類表來單獨存放分類信息,也許你會覺得很簡單,直接分類ID,分類名稱,備注什么的不就OK了。但是如果分類多了,分類下還可能出現子類,改如何處理?比如博客園的分類我感覺不怎么好用,比如我想在Asp.Net下新建一個MVC和WebForm子類;想在.NET類下建一個CLR,CFL,Ado.Net,UI四個子類是無法做到的。於是我想起以前設計分類表犯下的同樣的簡單化的錯誤,其實把簡單做到極致是很不容易的。如果當初能多一個ParentID列,也許就不至於現在加個子類都麻煩,最后把所有的和BS相關的都放在Asp.net類目下。

  

  這里的父類編號,讓分類變成了一個樹,雖然可能用不着子類,但是讓他冗余這,心理還是比較踏實。

 6、關於跨庫的細節

   1.關鍵字陷進:

  我們所設計的ER模型圖,有可能會移植到其他的數據庫,而且我們也為多數據庫支持使用了ORM或者抽象工廠,當我們覺得很完美的時候,如果一個不小心,又是大的體力勞動。比如你的SqlServer庫的User表有UID字段,DEFECT表有LEVEL(等級)字段,你發現ORACLE是不支持的,於是很郁悶的一個一個去手動修改,坑爹呀,建議建好庫后,跨庫各執行以下SQL,就知道不兼容在那兒了。

  2.存儲過程和觸發器的陷進

  這個優點就不說了,跨庫的后果就是全部重寫。所以大家經常說盡量不用存儲過程,NoSql是有道理的。

  3.自增長字段的陷進:

  自增長字段保證了對象的唯一,但是使用后給跨庫帶來的麻煩也是一堆一堆的,比如ORACLE不支持自增長。還有萬一客戶要求合並庫,於是數據的沖突帶給你的痛苦,只有自己懂得。檢驗統一使用VARCHAR(36)長度的GUID字段。GUID字段值由程序生成。

  4.表格命名不超過30個字符。

  這是ORACLE的要求,死的,沒有辦法。

  5.表名的規范以誰為主?

  曾經一個項目,表都設計好了,自我感覺很規范,最后ER文檔發給客戶的時候,客戶說這個規范我看了不習慣,必須按我們的規范來,說服了半天,對方說我們要源碼還要進行二次開發,必須的按他們的來。誰讓客戶是上帝,還是一個大客戶,沒有辦法,於是帶着滿腹的臟話100多張表全部一一改掉。可見如果碰到需要二次開發的,懂得內行的客戶,還是多留意的好。

  

 7、他山之石

  博客園的Jimmy Zhang對數據庫命名和設計說的很懇切,收益頗豐,特此引用,以資鼓勵。 

"我看過很多的開發人員設計出來的數據庫,給我的感覺就是:在他們眼里,數據庫的作用就如同它的名稱一樣――僅僅是用來存放數據的,除了不得不建的主鍵以外,什么都沒有...沒有 Check約束,沒有索引,沒有外鍵約束,沒有視圖,甚至沒有存儲過程。

在這里,我提出如下數據庫設計的建議:

如果要寫代碼來確保表中的行都是唯一的,就為表添加一個主鍵。
如果要寫代碼來確保表中的一個單獨的列是唯一的,就為表添加一個約束。
如果要寫代碼確定表中的列的取值只能屬於某個范圍,就添加一個Check約束。
如果要寫代碼來連接 父-子 表,就創建一個關系。
如果要寫代碼來維護“一旦父表中的一行發生變化,連帶變更子表中的相關行”,就啟用級聯刪除和更新。
如果要調用大量的Join來進行一個查詢,就創建一個視圖。
如果要逐條的寫數據庫操作的語句來完成一個業務規則,就使用存儲過程。
NOTE:這里我沒有提到觸發器,實踐證明觸發器會使數據庫迅速變得過於復雜,更重要的是觸發器難以調試,如果不小心建了個連環觸發器,就更讓人頭疼了,所以我更傾向於根本就不使用觸發器。" 

  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM