數據庫設計與建模最佳實踐(轉)

seailove 12年前發布 | 2K 次閱讀 NamedManager

摘要:本文針對數據庫設計中可讀性、易維護性及性能的要求,在數據庫對象命名、主鍵的設計、字段類型及長度設計等方面,總結出數據庫設計和建模最佳實踐。

關鍵詞:數據庫對象命名,主鍵的設計,字段類型及長度設計,最佳實踐范例

1. 引言

在企業級應用中,數據庫是重要的組成部分。同樣,數據庫設計也是設計工作的重中之重。好的數據庫設計可以簡化開發、降低維護成本,更可以提高系統性能。

在這篇文章里,我針對可讀性、易維護性及性能等方面,總結出以下數據庫設計和建模最佳實踐。

2. 數據庫對象命名

為對象命名并非一件輕松隨意的工作,下面的指導有利于構建一個規范的命名體系。

2.1 中文拼音還是英文單詞

中文拼音gzzbh是什么意思呢?如果改成worker_code即可一目了然。因為英文單詞語意比較精確,可讀性比中文拼音好,所以推薦用英文單詞給對象命名。

2.2 單詞間分隔

用下劃線分隔多個單詞。

由于Oracle等數據庫的對象名稱默認不區分大小寫(可以用雙引號來區分,但是會給開發造成不必要的麻煩),所以在編程開發中常用的駝式命名風格如:tableName不太適合給數據庫對象命名,因為對象的名稱是以TABLENAME形式存儲的。為了增強可讀性,單詞之間最好用下劃線分隔,如:tableName 最好書寫為 table_name。

2.3 避免使用關鍵字

用關鍵字命名對象會造成沖突。

如果一定用關鍵字命名對象,需要用雙引號,如:id 命名為 "id"。

避免關鍵字沖突的較好方案是增加前綴或者后綴, 這也是所有對象命名的推薦方案,如:“用戶編號”由"id" 改為更明確的user_id。

2.4 單數還是復數

用user還是users?

用child還是children?

英文名詞有單數和復數之分,那對象命名時怎么考慮呢?

幾乎所有的表都存儲1條以上的記錄,按這個事實,表名稱應該全用復數才對,考慮到英文名詞的復數變化比較復雜, 全用復數會加大設計者的負擔,并且容易出錯,從簡化工作考慮,對象命名統一用單數即可。

2.5 用簡寫還是全稱

dept_id, u_id分別代表什么意思呢?

對dept這個簡寫,一般情況下它是department的簡寫,而u_id的含義就不明確了。如果設計者把u_id改為user_id意義就明確了。

所以,除非是眾所周知的簡稱,一般情況下最好用更明確的全稱,不要讓別人猜測命名的含義,因為可讀性對系統的可維護性影響很大。

2.6 布爾型字段命名

有些字段的含義是代表布爾變量的:是、否;有、無。

為了強調這些字段的含義,可以在命名上增加 is_、has_、was_等前綴來增強可讀性,如:

enable用is_enable;

updatable用is_updatable;

2.7 主鍵字段命名

主鍵名稱推薦用:表名 + "_id" , 這樣能夠保證主鍵命名的唯一性和可讀性,如:

user表的主鍵用user_id;

department表的主鍵用dept_id;

2.8 外鍵字段命名

外鍵字段名稱最好與主表主鍵名稱一致。

這樣做可讀性好, 而且建模工具能夠根據主鍵、外鍵名稱相同很方便的建立外鍵關聯。

以上總結了數據庫對象命名的基本原則,在此基礎上,我們可以制定更詳細的數據庫命名規范。

3. 主鍵的設計

主鍵的設計作為表結構設計的重要組成部分,對開發和維護工作的影響不容忽視,值得我們仔細推敲。

3.1 主鍵的必要性

表一定要有主鍵嗎?答案是肯定的。

表缺少了主鍵,表結構不再完整。主鍵是我們唯一標識一條記錄的信息,在刪除、修改記錄時大都是通過主鍵來操作,沒有了主鍵,這些操作會很困難。

3.2 主鍵業務無關性

早期的數據庫設計,大多都是用業務信息做主鍵,在實踐中吃了很多虧后,大家更認同用業務無關的字段來做主鍵。

比如用訂單編號來做主鍵,開始大家覺得訂單編號應該是唯一的編號。隨著業務的變化,客戶在使用中提出,訂單可以作廢,但是該記錄不能刪除,而且需要新建一個具有相同訂單編號的訂單,麻煩來了。

這是用業務相關信息做主鍵的典型缺陷,用業務無關性的信息做主鍵就不會有這樣的問題。

3.3 單主鍵還是多主鍵

多主鍵其實是業務信息做主鍵時期的產物,既然我們確定了主鍵的業務無關性,利用單主鍵已經-能夠唯一標識一條記錄,所以多主鍵已?-沒有繼續使用的意義。

另外多主鍵還對開發造成很多麻煩,比如用Hibernate時,多主鍵需要單獨創建一個主鍵對象。

單主鍵方案不僅僅是說主表用單主鍵,子表也要用單主鍵,多對多的中間表也要用單主鍵。

3.4 主鍵生成方案

在確定單主鍵的方案后,我們如何產生一個業務無關的鍵值呢?

有以下幾種方案:

3.4.1 數值自增

數值自增是利用數據庫自身的特性,如:Oracle里的序列來保證唯一性。

這種方案依賴具體的數據庫產品,有移植的問題。該方案最大的隱患是數據集的合并問題,如果多個數據庫的數據需要合并,由于編號重復,會給合并工作帶來極大的困難。

3.4.2 最大值加1

每次從業務表里獲得最大的編號,然后加1作為下一條記錄的主鍵。這種方案只是產生編號的時期、方式與數值自增不同而已。

這種方案同樣有數據合并困難的隱患,而且由于每次都要計算最大值,這種方案還有性能和并發的問題。

3.4.3 自制加1

這種方案是最大值加1方案的改進,它建立一個專門的表來維護最大值,從而提高了性能。

這種方案同樣有數據合并困難的隱患,并且有并發問題。

3.4.4 全局唯一標識符GUID

GUID是由32位字符組成的全球唯一標識號,由特殊的算法來保證其計算結果的唯一性。GUID計算不依賴具體的數據庫產品,也沒有并發的問題。因為GUID值是全球唯一的,所以也不存在數據合并時主鍵沖突的問題。

這種方案的缺點是: 占用存儲空間,可讀性、表意性差。

GUID值是32位字母與數字的無序組合,很難記憶。如果用做數據字典主鍵,則很難看出具體的業務內容。     

以上是產生業務無關主鍵的幾種方案,通過對比不難得出,GUID做主鍵的方案最具有優勢。

4.字段類型及長度

字段的類型和長度與具體業務有關,而業務需求又常常變化,所以我們要設計一個健壯的結構來盡量避免需求變化對數據庫結構的影響。因為不同的數據庫字段類型也不同,這里我們討論的類型將以Oracle 8i為基準。

4.1 主鍵

前面提到主鍵采用GUID方案,所以主鍵至少要32位寬,有的GUID值中間有4個“-”字符,寬度是36位,向上取整,主鍵類型可以為40個字符。在Oracle 8i、9i數據庫里char(40)會用空格補位,Oracle 10G里char已?-不會補空格了,但考慮到兼容性,選取varchar2(40)。

4.2 布爾類型

對于布爾類型的字段建議number(1) 而不是char(1)。

char(1)里可以存儲0、1;Y/N;y/n等,由于值的不確定,特別是大小寫問題,會給開發帶來不必要的麻煩。

number(1)只能存0、1,意義很明顯;

4.3 一般文字類型

不要過分相信用戶需求,要盡量設計的寬松些,如:

車號:開發時是6位的,后來改為7位;

軸號:一般是8位,但是進口軸承有15位的;

軸承編號:一般是10位,后來又提出有20位的。

在用PowerBuilder開發系統時,數據窗口里會保存字段長度信息,如果改了數據庫字段的長度,數據窗口也必須刷新才行,需要重新編譯、發布系統。所以從系統易維護性考慮,一般文字字段類型可以長一些,可以與主鍵類型一致,都為40位字符寬,但是也不能太長,因為檢索數據時會消耗內存。

4.4 數值類型

一般直接用number ,不指定精度和小數位是個較好的選擇。

不指定精度的最大的好處是數值的精度和范圍是可以變化的。如果根據業務需求,字段類型設計為number(2,1),如果用戶在使用中提出小數點后要保留2位,則需要修改數據庫和程序。

在確定沒有小數位的情況下,最好指定精度,限制錄入內容必須為整型數值。

比如布爾類型的字段, 最好為 number(1)

用于排序的字段, 可以為 number(30)

用于Hibernate樂觀鎖定的version字段, 可以為 number(30)

4.5 備注和長文本

這2者的長度根據具體情況來定。

備注的長度一般在100 – 500 之間。

長文本的長度在1000 – 4000之間。

4.6 日期型

沒有什么特殊說明。

Oracle的日期類型不區分日期和時間類型。

4.7 LOB型

不要把lob類型字段放到業務表里,最好建立單獨的表存儲lob信息,這樣有利于系統的性能和數據庫的維護。

5.字段約束與默認值

數據庫大都支持約束控制,從數據完整性控制和性能來說,這個工作最好交給數據庫去做,盡量不要自己寫代碼。有些情況下,比如用戶名重復的提示,可以在保存前通過代碼先校驗一下,從而給出更友好的提示,如:該用戶已經注冊。

字段的默認值和非空限制最好設置上,否則保存的有可能是空值,這樣在開發時,對檢索出來的值每次都要先判斷空值,造成一點小麻煩。

6. 最佳實踐范例

<TABLE id=table2 class=content border=1 cellSpacing=0 borderColor=#cccccc cellPadding=0 width="100%" align=center height=100>

<TD class=wb3 width="38%">

<DIV align=center>反面示例</DIV></TD>

<TD class=wb3 width="40%">

<DIV align=center>最佳實踐</DIV></TD>

<TD class=wb3 width="22%">

<DIV align=center>改進說明</DIV></TD></TR>

 create table parent (
 pid number,
 pname varchar2(40),
 cdate date,
 enable char(1),
 uid varchar2(40)
);

 create table mf_db_parent (
 parent_id varchar2(40),
 parent_name varchar2(40) not null,
 create_date date default? sysdate not null ,
 is_enable number(1) default? 1 not null ,
 create_user_id varchar2(40)
);

用意義明確的全稱
用下劃線增強可讀性
非空約束
缺省值
主鍵類型
布爾變量命名及數據類型
</TD></TR>

 alter table parent
  add constraint pk_parent
  primary key(pid);

<TD class=wb3> alter table mf_db_parent
  add constraint pk_mf_db_parent
  primary key(parent_id);</TD>

<TD class=wb3>

</TD></TR>

<TD class=wb3> create table child(
 cid number(40),
 cname varchar2(40),
 pid number(40)
);</TD>

<TD class=wb3> create table mf_db_child(
 child_id varchar2(40),
 child_name varchar2(40) not null,
 parent_id varchar2(40)
);</TD>

<TD class=wb3>

用意義明確的全稱
用下劃線增強可讀性
非空約束
主鍵類型
</TD></TR>

<TD class=wb3> alter table child
  add constraint pk_child
  primary key(cid);</TD>

<TD class=wb3> alter table mf_db_child
  add constraint pk_mf_db_child
  primary key(child_id);</TD>

<TD class=wb3>

</TD></TR>

<TD class=wb3> alter table child
  add constraint fk_child_r_parent
  foreign key(pid)
   references t_parent (pid)
   on delete cascade;</TD>

<TD class=wb3> alter table mf_db_child
  add constraint fk_child_r_parent
  foreign key(parent_id)
   references mf_db_parent (parent_id)
   on delete cascade;</TD>

<TD class=wb3>

</TD></TR>

<TD class=wb3> </TD>

<TD class=wb3> create index idx_mf_db_child_parent_id
  on mf_db_child(parent_id);</TD>

<TD class=wb3>

在外鍵上建立索引. 避免主表更新時鎖定整個子表
</TD></TR></TBODY></TABLE>

 本文由用戶 seailove 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!