天天看點

資料庫設計漫談

 引言

  資料庫設計規範,仁者見仁,但是有共同的目标都是想要更加簡潔清新,可維護可擴充等等。有時候設計的時候沒有想到,等到開發的時候,或者釋出完了,客戶幫我們發現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:這裡我沒有提到觸發器,實踐證明觸發器會使資料庫迅速變得過于複雜,更重要的是觸發器難以調試,如果不小心建了個連環觸發器,就更讓人頭疼了,是以我更傾向于根本就不使用觸發器。"