1. 程式人生 > >談談實際專案中對 資料庫設計 的一些思考

談談實際專案中對 資料庫設計 的一些思考

注:本人開發經驗尚淺,下文主要談的是自己的一些想法,不足之處請指出。

最近半年時間都花在管理系統的開放上面,對資料庫的設計有一些自己的想法,在我看來資料庫設計的key point就是妥協。一個設計的比較好的資料庫都是在業務邏輯、設計規約和便於開發這三者之前來回考量,從而獲得3-win的結果。下面主要是在思考和總結的點。

如何設計出高靈活性的資料庫

可以說在專案交付前,需求不斷在變,如何在需求改變的同時儘可能減少對錶結構的修改是我現在考慮的問題。對於一般情況而言,在設計的時候我們可以適當新增一些預留的欄位,需求改變的時候可以用上,或者說是新增不足的欄位。

但是也會出現一些比較麻煩的情況——舊錶不足以進行修改維護,需要區域性重新設計,這就會給開發帶來比較多的麻煩,因為大多現在我們都會使用ORM。

eg1:

在貸款的業務流程中,每一筆借款申請需要進行稽核。

起初,參與的稽核角色只有2名,所以我選擇了,直接新增欄位到貸款申請表中。

之後,參與稽核的角色數變多,並且稽核產生的業務引數變多(例如各類附件、意見等),直接新增欄位到貸款表中顯得不那麼合理,同時不利於稽核流程的體現。因此,這一部分需要單獨重新設計,根據業務流的需要,將稽核的事件單獨拉出來一張表,通過外來鍵關聯到申請表中。(流程複復雜可參考工作流引擎的設計思路)

這個案例裡,最初的設計,覺得既然稽核人數少,直接把稽核資訊新增到申請表中即可,不需要單獨建表,也方便了開發,但是後來的需求改變就有單措手不及了。

這裡就感覺很矛盾,同時也發現數據庫的設計居然也和軟體工程那一套理論驚人的相似。在軟體工程裡面,我們經常會聽到一句話“低耦合、高內聚”,一般是提醒我們,儘可能保證模組與模組之間相互獨立,減少不必要的依賴關係。

這也就是困擾這我的地方,如果一開始設計就考慮單一職責,分成兩張表,未免顯得有些複雜化了,也不利於開發(需要維護1-n的關係)。畢竟程式碼一定程度上是可以重構的。

資料庫原理在實際設計中的運用

我們在《資料庫原理與設計》中學過很多的概念,由於實驗環境的限制(資料量小、課設的業務偏簡單etc.),導致很多概念在那時真的就變成了一個死記硬背的概念。這點或許隨著開發經驗的增長會豁然開朗吧。下面介紹幾個,替他們刷一下存在感,內容都很淺,但也希望對在學習資料庫的有一點幫助吧。

正規化(NF)

1NF的定義為:符合1NF的關係中的每個屬性都不可再分 2NF在1NF的基礎之上,消除了非主屬性對於碼的部分函式依賴。 3NF在2NF的基礎之上,消除了非主屬性對於碼的傳遞函式依賴。 …

正規化的概念大家肯定背的很熟,本人研究生面試也考了這個。這一點其實大家在按設計資料庫的時候都已經用上了。明確實體間的關係,畫E-R圖,其實就是對正規化的運用。

索引(Index)

Index,面試的時候也經常會問到,但是呢學他的時候基本上感覺不到他的存在,畢竟測試的資料集太小了。底層的原理牽扯到資料結構,這裡就不展開了。但是需要知道的是:

  • 需要做表連線的欄位,需要新增index。

  • 經常需要查詢的欄位,需要新增index。

  • 很多欄位需要聯查的時候,需要新增組合查詢。

下面是一個實際專案中的例子,可以感受下:

有一個地方需要資料展示,需要多表聯查。下面就是建索引和不建索引的區別了。

事務(Transaction)

資料庫事務(Database Transaction) ,是指作為單個邏輯工作單元執行的一系列操作,要麼完全地執行,要麼完全地不執行。

事務處理可以確保除非事務性單元內的所有操作都成功完成,否則不會永久更新面向資料的資源。通過將一組相關操作組合為一個要麼全部成功要麼全部失敗的單元,可以簡化錯誤恢復並使應用程式更加可靠。一個邏輯工作單元要成為事務,必須滿足所謂的ACID(原子性、一致性、隔離性和永續性)屬性。事務是資料庫執行中的邏輯工作單位,由DBMS中的事務管理子系統負責事務的處理。

這個寫過管理系統的應該都明白。大概就是下面這個模型。JDBC中就有,在ssh或ssm中可以配合註解或配置檔案來實現。

BeginTransaction{
    // to-do 
    commit
}catch{
    rollback
}

一般在多表操作時必須使用事務!例如級聯刪除,多表修改。如果不使用,一旦出現異常,會導致資料不一致!

具體可參考:

http://www.mybatis.org/spring/zh/transactions.html https://www.cnblogs.com/Weagle/p/5264450.html

鎖(lock)

鎖根據不同的法,可以分為樂觀鎖、悲觀鎖。也可以分為共享鎖和排它鎖。

一般在出現併發問題的時候會用到鎖,具體可以看我另外一篇:

http://www.cnblogs.com/Sinte-Beuve/p/7631745.html

解決小規模併發下單問題。裡面詳細講了鎖的描述和實現。

筆者經驗有限,其實還有很多概念類似,資料庫安全,容災備份等沒有接觸到的,希望以後有機會可以補全。

冗餘欄位的利用

冗餘欄位的利用,其實僅僅是為了開發方便的一個tip。

具體可以看上面一個小例子。一筆借款需要經過很多人的稽核才能通過。因此存在借款表和稽核流程表,兩者一對多。通過借款單號可以去稽核表中查詢其所有的稽核情況。

那麼此時,我選擇在借款表中存稽核流程表id(外來鍵)的同時,存下當前這一筆借款的稽核資訊。這麼做的好處就是,我不需要去做表連線,直接可以查詢到借款的當前進行到哪一流程,或者說申請失敗,失敗在哪一流程。從開發的角度,如果有一個頁面需要顯示出借款的當前流程,那就可以輕鬆查詢到了。

畢竟,處理表連線操作是相對麻煩的,同時效率並不高。因此在儲存條件允許的情況下,可以通過適當冗餘來減少開發中的麻煩。這裡就體現了資料庫設計的一種妥協,現在存在冗餘是違反了2-NF的,所以有的時候就需要憑經驗來進行調整。對於不是頻繁需要修改的欄位,是可以適當冗餘存取的,反之,經常需要修改的欄位,如果冗餘存取,一不小心很容易造成資料的不一致,就不建議了。

檢視與冗餘表

在談冗餘表前,先來看幾個在Java OO中的概念,拋磚引玉一下。

VO(View Object):檢視物件,用於展示層,它的作用是把某個指定頁面(或元件)的所有資料封裝起來。 DTO(Data Transfer Object):資料傳輸物件,這個概念來源於J2EE的設計模式,原來的目的是為了EJB的分散式應用提供粗粒度的資料實體,以減少分散式呼叫的次數,從而提高分散式呼叫的效能和降低網路負載,但在這裡,我泛指用於展示層與服務層之間的資料傳輸物件。 DO(Domain Object):領域物件,就是從現實世界中抽象出來的有形或無形的業務實體。 PO(Persistent Object):持久化物件,它跟持久層(通常是關係型資料庫)的資料結構形成一一對應的對映關係,如果持久層是關係型資料庫,那麼,資料表中的每個欄位(或若干個)就對應PO的一個(或若干個)屬性。

首先,這些物件其實是和資料庫中資料表存在一些關係的。我想說的是,在資料展示或資料傳輸時,可能會涉及多張表,這個時候我們會做的就是建立一個新的Object,然後講多張表對應的持久化物件放進去,或者直接從多個表的持久化物件中抽取需要的欄位。這個物件也就是上面提到的VO和DTO了。

這種做法同時也可以在資料庫進行實現,那就是冗餘表。其實也就是把很多張表的資料統一到一張表中去,為的也是查詢和更新的方便。

之前遇到以下這個業務:

存在訂單、訂單明細之前一對多,訂單又需要和卡繫結,一對多。現在我需要將資料一起顯示出來看,同時粒度要最細,以訂單明細和卡號的形式顯示出來,t同時可更新。

eg:有訂單記錄A ,訂單明細B1、B2,綁定了兩張卡C1、C2,那麼顯示效果如下

C1 A B1
C1 A B2
C2 A B1 
C2 A B2

這個時候考慮到如果建立一個DTO來儲存這些資料的話,在查詢組裝上需要花很多的時間,於是就直接建立了一張冗餘表,用於維護這層關係。那麼通過這層冗餘表查詢出來的結果就是可以直接用於顯示的,更新的時候也只需要直接找到外來鍵進行更新就可以了。

我覺得使用DTO還是冗餘表,完全就是取決於開發上的的便利以及是否需要對資料進行持久化了。

同樣的,之前的例子提到是需要對資料進行更新或刪除的,如果僅僅是為了資料的展示,例如一些統計資料的展示。那麼就不需要冗餘表了。資料庫裡面有個專有的概念——檢視。比如說查詢每天每年每季度的銷售情況等等,直接用sql建立檢視,查詢以天為單位的結果,展示的時候直接從view中取資料即可,將複雜的sql語句就留在資料庫那邊了。

當然建立檢視若使用大量表連線時,記得建立index進行優化,否則查詢效率會很低。

資料字典與程式碼表

資料字典與程式碼表其實儲存的就是一些常量了。

資料字典一般兩張表,一張存類別,一張存鍵值對。資料字典一般用於存取一些不太會變的資料,例如性別、訂單狀態etc。而且這些資料量比較小,可以統一儲存。

程式碼表,一般都是各管各儲存的。例如文章分類表、標籤表、省市區程式碼、商品分類etc。這些本來資料就大,並且有一定的業務性,有些會隨著業務的擴大而變動,因此單獨存表會比較好。

總結

一下子碼了很多字,主要寫了一些資料庫中的一些概念在實際應用上的體現。表達了最近一段時間對資料庫設計上面的一些思考和見解。人是會思想的蘆葦,實踐是檢驗真理的唯一標準,嗯……