一 Codd的RDBMS12法則——RDBMS的起源html
二 關係型數據庫設計階段sql
三 設計原則數據庫
四 命名規則安全
數據庫設計,一個軟件項目成功的基石。不少從業人員都認爲,數據庫設計其實不那麼重要。現實中的情景也至關雷同,開發人員的數量是數據庫設計人員的數倍。多數人使用數據庫中的一部分,因此也會把數據庫設計想的如此簡單。其實否則,數據庫設計也是門學問。網絡
從筆者的經歷看來,筆者更同意在項目早期由開發者進行數據庫設計(後期調優須要DBA)。根據筆者的項目經驗,一個精通OOP和ORM的開發者,設計的數據庫每每更爲合理,更能適應需求的變化,若是追其緣由,筆者我的猜想是由於數據庫的規範化,與OO的部分思想雷同(如內聚)。而DBA,設計的數據庫的優點是能將DBMS的能力發揮到極致,可以使用SQL和DBMS實現不少程序實現的邏輯,與開發者相比,DBA優化過的數據庫更爲高效和穩定。如標題所示,本文旨在分享一名開發者的數據庫設計經驗,並不涉及複雜的SQL語句或DBMS使用,所以也不會侷限到某種DBMS產品上。真切地但願這篇文章對開發者能有所幫助,也但願讀者能幫助筆者查漏補缺。數據結構
Edgar Frank Codd(埃德加·弗蘭克·科德)被譽爲「關係數據庫之父」,並由於在數據庫管理系統的理論和實踐方面的傑出貢獻於1981年獲圖靈獎。在1985年,Codd 博士發佈了12條規則,這些規則簡明的定義出一個關係型數據庫的理念,它們被做爲全部關係數據庫系統的設計指導性方針。框架
(一)規劃階段數據庫設計
規劃階段的主要工做是對數據庫的必要性和可行性進行分析。肯定是否須要使用數據庫,使用哪一種類型的數據庫,使用哪一個數據庫產品。
(二)概念階段
概念階段的主要工做是收集並分析需求。識別需求,主要是識別數據實體和業務規則。對於一個系統來講,數據庫的主要包括業務數據和非業務數據,而業務數據的定義,則依賴於在此階段對用戶需求的分析。須要儘可能識別業務實體和業務規則,對系統的總體有初步的認識,並理解數據的流動過程。理論上,該階段將參考或產出多種文檔,好比「用例圖」,「數據流圖」以及其餘一些項目文檔。若是可以在該階段產出這些成果,無疑將會對後期進行莫大的幫助。固然,不少文檔已超出數據庫設計者的考慮範圍。並且,若是你並不精通該領域以及用戶的業務,那麼請放棄本身獨立完成用戶需求分析的想法。用戶並非技術專家,而當你自身不能扮演「業務顧問」的角色時,請你選擇與項目組的相關人員合做,或者將其視爲風險呈報給PM。再次強調,大多數狀況,用戶只是行業從業者,而非職業技術人員,咱們僅僅從用戶那裏收集需求,而非依賴於用戶的知識。
記錄用戶需求時,可使用一些技巧,固然這部份內容有些可能會超出數據庫設計人員的職責:
此外,必須嚴謹處理業務規則,並詳細記錄。在以後的階段,將會根據這些業務規則進行設計。
當該階段結束時,你應該可以回答如下問題:
而且獲得以下信息:
(三)邏輯階段
邏輯階段的主要工做是繪製E-R圖,或者說是建模。建模工具不少,有不一樣的圖形表示方法和軟件。這些工具和軟件的使用並不是關鍵,筆者也不建議讀者花大量時間在建模方法的選擇上。對於大多數應用來講,E-R圖足以描述實體間的關係。建模關鍵是思想而不是工具,軟件只是起到輔助做用,識別實體關係纔是本階段的重點。
除了實體關係,咱們還應該考慮屬性的域(值類型、範圍、約束)
(四)實現階段
實現階段主要針對選擇的RDBMS定義E-R圖對應的表,考慮屬性類型和範圍以及約束。
(五)物理階段
物理階段是一個驗證並調優的階段,是在實際物理設備上部署數據庫,並進行測試和調優。
(一)下降對數據庫功能的依賴
功能應該由程序實現,而非DB實現。緣由在於,若是功能由DB實現時,一旦更換的DBMS不如以前的系統強大,不能實現某些功能,這時咱們將不得不去修改代碼。因此,爲了杜絕此類狀況的發生,功能應該有程序實現,數據庫僅僅負責數據的存儲,以達到最低的耦合。
(二)定義實體關係的原則
當定義一個實體與其餘實體之間的關係時,須要考量以下:
關係與表數量
(三)列意味着惟一的值
若是表示座標(0,0),應該使用兩列表示,而不是將「0,0」放在1個列中。
(四)列的順序
列的順序對於表來講可有可無,可是從習慣上來講,採用「主鍵+外鍵+實體數據+非實體數據」這樣的順序對列進行排序顯然能獲得比較好的可讀性。
(五)定義主鍵和外鍵
數據表必須定義主鍵和外鍵(若是有外鍵)。定義主鍵和外鍵不只是RDBMS的要求,同時也是開發的要求。幾乎全部的代碼生成器都須要這些信息來生成經常使用方法的代碼(包括SQL文和引用),因此,定義主鍵和外鍵在開發階段是必須的。之因此說在開發階段是必須的是由於,有很多團隊出於性能考慮會在進行大量測試後,在保證參照完整性不會出現大的缺陷後,會刪除掉DB的全部外鍵,以達到最優性能。筆者認爲,在性能沒有出現問題時應該保留外鍵,而即使性能真的出現問題,也應該對SQL文進行優化,而非放棄外鍵約束。
(六)選擇鍵
1 人工鍵與天然鍵
人工健——實體的非天然屬性,根據須要由人強加的,如GUID,其對實體毫無心義;天然健——實體的天然屬性,如身份證編號。
人工鍵的好處:
人工鍵的缺點:
筆者建議所有使用人工鍵。緣由以下:
筆者的另外一個建議是——每張表都須要有一個對用戶而言有意義的天然鍵,在特殊狀況下也許找不到這樣一個項,此時可使用複合鍵。這個鍵我在程序中並不會使用其做爲惟一標識,可是卻能夠在對數據庫直接進行查詢時使用。
使用人工鍵的另外一根弊端,主要源自對查詢性能的考量,所以選擇人工鍵的形式(列的類型)很重要:
2 智能健與非智能鍵
智能鍵——鍵值包含額外信息,其根據某種約定好的編碼規範進行編碼,從鍵值自己能夠獲取某些信息;非智能鍵,單純的無心義鍵值,如自增的數字或GUID。
智能鍵是一把雙刃劍,開發人員偏心這種包含信息的鍵值,程序盼望着其中潛在的數據;數據庫管理員或者設計者則討厭這種智能鍵,緣由也是很顯然的,智能鍵對數據庫是潛在的風險。前面提到,數據庫設計的原則之一是不要把具備獨立意義的值的組合實現到一個單一的列中,應該使用多個獨立的列。數據庫設計者,更但願開發人員經過拼接多個列來獲得智能鍵,即以複合主鍵的形式給開發人員使用,而不是將一個列的值分解後使用。開發人員應該接受這種數據庫設計,可是不少開發者卻想不明白二者的優略。筆者認爲,使用單一列實現智能鍵存在這樣一個風險,就是咱們可能在設計階段沒法預期到編碼規則可能會在後期發生變化。好比,構成智能鍵的局部鍵的值用完而引發規則變化或者長度變化,這種編碼規則的變化對於程序的有效性驗證與智能鍵解析是破壞性的,這是系統運維人員最不但願看到的。因此筆者建議若是須要智能鍵,請在業務邏輯層封裝(使用只讀屬性),不要再持久化層實現,以免上述問題。
(七)是否容許NULL
關於NULL咱們須要瞭解它的幾個特性:
那麼咱們是否應該容許列爲空呢?筆者認爲這個問題的答案受到咱們的開發語言的影響。以C#爲例,由於引入了可空類型來處理數據庫值類型爲NULL的情形,因此是否容許爲空對開發者來講意義並不大。但有一點必須注意,就是驗證非空必需要在程序集進行處理,而不應依賴於DBMS的非空約束,必須確保完整數據(全部必須的屬性均被賦值)到達DB(所謂的「安全區」,咱們必須定義在多層系統中那些區域獲得的數據是安全而純淨的)。
(八)屬性切割
一種錯誤想法是,屬性與列是1:1的關係。對於開發者,咱們公開屬性而非字段。舉個例子來講,對於實體「員工」有「名字」這一屬性,「名字」能夠再被分解爲「姓」和「名」,對於開發人員來講,顯然第二種數據結構更受青睞(「姓」和「名」做爲兩個字段)。因此,在設計時咱們也應該根據須要考慮是否切割屬性。
(九)規範化——範式
當筆者還在大學時,範式是學習關係型數據庫時最頭疼的問題。我想也許會有讀者仍然不理解範式的價值,簡單來講——範式將幫助咱們來保證數據的有效性和完整性。規範化的目的以下:
規範化旨在——挑出複雜的實體,從中抽取出簡單的實體。這個過程一直持續下去,直到數據庫中每一個表都只表明一件事物,而且表中每一個描述的都是這件事物爲止。
1 規範化實體和屬性(去除冗餘)
1NF:每一個屬性都只應表示一個單一的值,而非多個值。
須要考慮幾點:
當前設計不符合1NF的「臭味」:
2 屬性間的關係(去除冗餘)
2NF-實體必須符合1NF,每一個屬性描述的東西都必須針對整個鍵(能夠理解爲oop中類型屬性的內聚性)。
當前設計不符合2NF的「臭味」:
3NF-實體必須符合2NF,非鍵屬性不能描述其餘非鍵屬性。(與2NF不一樣,3NF處理的是非鍵屬性和非鍵屬性之間的關係,而不是和鍵屬性之間的關係。
當前設計不符合3NF的「臭味」:
BCNF-實體知足第一範式,全部屬性徹底依賴於某個鍵,若是全部的斷定都是一個鍵,則實體知足BCNF。(BCNF簡單地擴展了之前的範式,它說的是:一個實體可能有若干個鍵,全部屬性都必須依賴於這些鍵中的一個,也能夠理解爲「每一個鍵必須惟一標識實體,每一個非鍵熟悉必須描述實體。」
3 去除實體組合鍵中的冗餘
4NF-實體必須知足BCNF,在一個屬性與實體的鍵之間,多值依賴(一條記錄在整個表的惟一性由多個值組合起來決定的)不能超過一個。
當前設計不符合4NF的「臭味」:
4 儘可能將全部關係分解爲二元關係
5NF-實體必須知足4NF,當分解的信息無損的時候,確保全部關係都被分解爲二元關係。
5NF保證在第四範式中存在的任何能夠分解爲實體的三元關係都被分解。有的三元關係能夠在不丟失信息的前提下被分解爲二元關係,當分解爲兩個二元關係的過程要丟失信息時,關係被宣稱爲處於第四範式中。因此,第五範式建議是,最好把現有的三元關係都分解爲3個二元關係。
須要注意的是,規範化的結果多是更多的表,更復雜的查詢。所以,處理到何種程度,取決於性能和數據架構的多方考量。建議規範化到第四範式,緣由是5NF的判斷太過隱晦。例如:表X(老師,學生,課程)是一個三元關係,能夠分解爲表A(老師,學生),表B(學生,課程),表C(老師,課程)。表X表示某個老師是上某個學生的某個課程的老師;表A表示老師教學生;表B表示學生上課;表C表示老師教課。單獨看是沒法發現問題的,可是從數據出發,"表X=表A+表B+表C"並不必定成立,即不能經過鏈接構建分解前的數據。由於可能有多種組合,喪失了表X反饋出的業務規則。這種現象,容易在設計階段被忽略,但好在在開放階段會被顯現,並且並不常常發生。
推薦作法:
(十)選擇數據類型(MS SQL 2008)
MS SQL的經常使用類型:
精確數字 | 不會發生精度損失 | bit tinyint smallint int bigint decimal |
近似數字 | 對於極值可能發生精度損失 | float(N) real |
日期和時間 | date time smalldatetime datetime datetime2 datetimeoffset | |
二進制數據 | bingary(N) varbinary(N) varbinary(max) | |
字符(串)數據 | char(N) varchar(N) varchar(max) nchar(N) nvarchar(N) nvarchar(max) | |
存儲任意數據 | sql_variant | |
時間戳 | timestamp | |
GUID | uniqueidentifier | |
XML | 不要試圖使用該類型規避1NF | xml |
空間數據 | geometry geography | |
層次數據 | heirarchyid |
MS SQL中不在支持的或糟糕的類型選擇
經常使用類型選擇:
類型選擇的最基本規則是選擇知足須要的最輕的類型,由於這樣查詢更快。
bool | 建議使用bit而非char(1),由於開發語言對其支持覺好,能夠直接映射爲bool或bool?。 |
大值數據 | 使用全部備選類型中最小的那種,類型越大,查詢越慢,當字節大於8000時,應使用max。 |
主鍵 | 自增主鍵根據預期範圍選擇int或bigint,GUID使用uniqueidentifier而非varchar(N)。 |
(十一)優化並行
設計DB時就應該考慮到對並行進行優化,好比,MS SQL中的timestamp類型就是極好的選擇。