寫給開發(fā)者看的關(guān)系型數(shù)據(jù)庫(kù)設(shè)計(jì)
數(shù)據(jù)庫(kù)設(shè)計(jì),一個(gè)軟件項(xiàng)目成功的基石。很多從業(yè)人員都認(rèn)為,數(shù)據(jù)庫(kù)設(shè)計(jì)其實(shí)不那么重要?,F(xiàn)實(shí)中的情景也相當(dāng)雷同,開發(fā)人員的數(shù)量是數(shù)據(jù)庫(kù)設(shè)計(jì)人員的數(shù)倍。多數(shù)人使用數(shù)據(jù)庫(kù)中的一部分,所以也會(huì)把數(shù)據(jù)庫(kù)設(shè)計(jì)想的如此簡(jiǎn)單。其實(shí)不然,數(shù)據(jù)庫(kù)設(shè)計(jì)也是門學(xué)問。
從筆者的經(jīng)歷看來,筆者更贊成在項(xiàng)目早期由開發(fā)者進(jìn)行數(shù)據(jù)庫(kù)設(shè)計(jì)(后期調(diào)優(yōu)需要DBA)。根據(jù)筆者的項(xiàng)目經(jīng)驗(yàn),一個(gè)精通OOP和ORM的開發(fā)者,設(shè)計(jì)的數(shù)據(jù)庫(kù)往往更為合理,更能適應(yīng)需求的變化,如果追其原因,筆者個(gè)人猜測(cè)是因?yàn)閿?shù)據(jù)庫(kù)的規(guī)范化,與OO的部分思想雷同(如內(nèi)聚)。而DBA,設(shè)計(jì)的數(shù)據(jù)庫(kù)的優(yōu)勢(shì)是能將DBMS的能力發(fā)揮到極致,能夠使用SQL和DBMS實(shí)現(xiàn)很多程序?qū)崿F(xiàn)的邏輯,與開發(fā)者相比,DBA優(yōu)化過的數(shù)據(jù)庫(kù)更為高效和穩(wěn)定。如標(biāo)題所示,本文旨在分享一名開發(fā)者的數(shù)據(jù)庫(kù)設(shè)計(jì)經(jīng)驗(yàn),并不涉及復(fù)雜的SQL語句或 DBMS使用,因此也不會(huì)局限到某種DBMS產(chǎn)品上。真切地希望這篇文章對(duì)開發(fā)者能有所幫助,也希望讀者能幫助筆者查漏補(bǔ)缺。
一 Codd的RDBMS12法則——RDBMS的起源
Edgar Frank Codd(埃德加·弗蘭克·科德)被譽(yù)為“關(guān)系數(shù)據(jù)庫(kù)之父”,并因?yàn)樵跀?shù)據(jù)庫(kù)管理系統(tǒng)的理論和實(shí)踐方面的杰出貢獻(xiàn)于1981年獲圖靈獎(jiǎng)。在1985 年,Codd 博士發(fā)布了12條規(guī)則,這些規(guī)則簡(jiǎn)明的定義出一個(gè)關(guān)系型數(shù)據(jù)庫(kù)的理念,它們被作為所有關(guān)系數(shù)據(jù)庫(kù)系統(tǒng)的設(shè)計(jì)指導(dǎo)性方針。
- 信息法則 關(guān)系數(shù)據(jù)庫(kù)中的所有信息都用唯一的一種方式表示——表中的值。
- 保證訪問法則 依靠表名、主鍵值和列名的組合,保證能訪問每個(gè)數(shù)據(jù)項(xiàng)。
- 空值的系統(tǒng)化處理 支持空值(NULL),以系統(tǒng)化的方式處理空值,空值不依賴于數(shù)據(jù)類型。
- 基于關(guān)系模型的動(dòng)態(tài)聯(lián)機(jī)目錄 數(shù)據(jù)庫(kù)的描述應(yīng)該是自描述的,在邏輯級(jí)別上和普通數(shù)據(jù)采用同樣的表示方式,即數(shù)據(jù)庫(kù)必須含有描述該數(shù)據(jù)庫(kù)結(jié)構(gòu)的系統(tǒng)表或者數(shù)據(jù)庫(kù)描述信息應(yīng)該包含在用戶可以訪問的表中。
- 統(tǒng)一的數(shù)據(jù)子語言法則 一個(gè)關(guān)系數(shù)據(jù)庫(kù)系統(tǒng)可以支持幾種語言和多種終端使用方式,但必須至少有一種語言,它的語句能夠一某種定義良好的語法表示為字符串,并能全面地支持以下所有規(guī)則:數(shù)據(jù)定義、視圖定義、數(shù)據(jù)操作、約束、授權(quán)以及事務(wù)。(這種語言就是SQL)
- 視圖更新法則 所有理論上可以更新的視圖也可以由系統(tǒng)更新。
- 高級(jí)的插入、更新和刪除操作 把一個(gè)基礎(chǔ)關(guān)系或派生關(guān)系作為單個(gè)操作對(duì)象處理的能力不僅適應(yīng)于數(shù)據(jù)的檢索,還適用于數(shù)據(jù)的插入、修改個(gè)刪除,即在插入、修改和刪除操作中數(shù)據(jù)行被視作集合。
- 數(shù)據(jù)的物理獨(dú)立性 不管數(shù)據(jù)庫(kù)的數(shù)據(jù)在存儲(chǔ)表示或訪問方式上怎么變化,應(yīng)用程序和終端活動(dòng)都保持著邏輯上的不變性。
- 數(shù)據(jù)的邏輯獨(dú)立性 當(dāng)對(duì)表做了理論上不會(huì)損害信息的改變時(shí),應(yīng)用程序和終端活動(dòng)都會(huì)保持邏輯上的不變性。
- 數(shù)據(jù)完整性的獨(dú)立性 專用于某個(gè)關(guān)系型數(shù)據(jù)庫(kù)的完整性約束必須可以用關(guān)系數(shù)據(jù)庫(kù)子語言定義,而且可以存儲(chǔ)在數(shù)據(jù)目錄中,而非程序中。
- 分布獨(dú)立性 不管數(shù)據(jù)在物理是否分布式存儲(chǔ),或者任何時(shí)候改變分布策略,RDBMS的數(shù)據(jù)操縱子語言必須能使應(yīng)用程序和終端活動(dòng)保持邏輯上的不變性。
- 非破壞性法則 如果一個(gè)關(guān)系數(shù)據(jù)庫(kù)系統(tǒng)支持某種低級(jí)(一次處理單個(gè)記錄)語言,那么這個(gè)低級(jí)語言不能違反或繞過更高級(jí)語言(一次處理多個(gè)記錄)規(guī)定的完整性法則或約束,即用戶不能以任何方式違反數(shù)據(jù)庫(kù)的約束。
二 關(guān)系型數(shù)據(jù)庫(kù)設(shè)計(jì)階段
(一)規(guī)劃階段
規(guī)劃階段的主要工作是對(duì)數(shù)據(jù)庫(kù)的必要性和可行性進(jìn)行分析。確定是否需要使用數(shù)據(jù)庫(kù),使用哪種類型的數(shù)據(jù)庫(kù),使用哪個(gè)數(shù)據(jù)庫(kù)產(chǎn)品。
(二)概念階段
概念階段的主要工作是收集并分析需求。識(shí)別需求,主要是識(shí)別數(shù)據(jù)實(shí)體和業(yè)務(wù)規(guī)則。對(duì)于一個(gè)系統(tǒng)來說,數(shù)據(jù)庫(kù)的主要包括業(yè)務(wù)數(shù)據(jù)和非業(yè)務(wù)數(shù)據(jù),而業(yè)務(wù)數(shù)據(jù)的定義,則依賴于在此階段對(duì)用戶需求的分析。需要盡量識(shí)別業(yè)務(wù)實(shí)體和業(yè)務(wù)規(guī)則,對(duì)系統(tǒng)的整體有初步的認(rèn)識(shí),并理解數(shù)據(jù)的流動(dòng)過程。理論上,該階段將參考或產(chǎn)出多種文檔,比如“用例圖”,“數(shù)據(jù)流圖”以及其他一些項(xiàng)目文檔。如果能夠在該階段產(chǎn)出這些成果,無疑將會(huì)對(duì)后期進(jìn)行莫大的幫助。當(dāng)然,很多文檔已超出數(shù)據(jù)庫(kù)設(shè)計(jì)者的考慮范圍。而且,如果你并不精通該領(lǐng)域以及用戶的業(yè)務(wù),那么請(qǐng)放棄自己獨(dú)立完成用戶需求分析的想法。用戶并不是技術(shù)專家,而當(dāng)你自身不能扮演“業(yè)務(wù)顧問”的角色時(shí),請(qǐng)你選擇與項(xiàng)目組的相關(guān)人員合作,或者將其視為風(fēng)險(xiǎn)呈報(bào)給PM。再次強(qiáng)調(diào),大多數(shù)情況,用戶只是行業(yè)從業(yè)者,而非職業(yè)技術(shù)人員,我們僅僅從用戶那里收集需求,而非依賴于用戶的知識(shí)。
記錄用戶需求時(shí),可以使用一些技巧,當(dāng)然這部分內(nèi)容有些可能會(huì)超出數(shù)據(jù)庫(kù)設(shè)計(jì)人員的職責(zé):
- 努力維護(hù)一系列包含了系統(tǒng)設(shè)計(jì)和規(guī)格說明信息的文檔,如會(huì)議記錄、訪談?dòng)涗?、關(guān)鍵用戶期望、功能規(guī)格、技術(shù)規(guī)格、測(cè)試規(guī)格等。
- 頻繁與干系人溝通并收集反饋。
- 標(biāo)記出你自己添加的,不屬于客戶要求的,未決內(nèi)容。
- 與所有關(guān)鍵干系人盡快確認(rèn)項(xiàng)目范圍,并力求凍結(jié)需求。
此外,必須嚴(yán)謹(jǐn)處理業(yè)務(wù)規(guī)則,并詳細(xì)記錄。在之后的階段,將會(huì)根據(jù)這些業(yè)務(wù)規(guī)則進(jìn)行設(shè)計(jì)。
當(dāng)該階段結(jié)束時(shí),你應(yīng)該能夠回答以下問題:
- 需要哪些數(shù)據(jù)?
- 數(shù)據(jù)該被怎樣使用?
- 哪些規(guī)則控制著數(shù)據(jù)的使用?
- 誰會(huì)使用何種數(shù)據(jù)?
- 客戶想在核心功能界面或者報(bào)表上看到哪些內(nèi)容?
- 數(shù)據(jù)現(xiàn)在在哪里?
- 數(shù)據(jù)是否與其他系統(tǒng)有交互、集成或同步?
- 主題數(shù)據(jù)有哪些?
- 核心數(shù)據(jù)價(jià)值幾何,對(duì)可靠性的要求程度?
并且得到如下信息:
- 實(shí)體和關(guān)系
- 屬性和域
- 可以在數(shù)據(jù)庫(kù)中強(qiáng)制執(zhí)行的業(yè)務(wù)規(guī)則
- 需要使用數(shù)據(jù)庫(kù)的業(yè)務(wù)過程
(三)邏輯階段
邏輯階段的主要工作是繪制E-R圖,或者說是建模。建模工具很多,有不同的圖形表示方法和軟件。這些工具和軟件的使用并非關(guān)鍵,筆者也不建議讀者花大量時(shí)間在建模方法的選擇上。對(duì)于大多數(shù)應(yīng)用來說,E-R圖足以描述實(shí)體間的關(guān)系。建模關(guān)鍵是思想而不是工具,軟件只是起到輔助作用,識(shí)別實(shí)體關(guān)系才是本階段的重點(diǎn)。
除了實(shí)體關(guān)系,我們還應(yīng)該考慮屬性的域(值類型、范圍、約束)
(四)實(shí)現(xiàn)階段
實(shí)現(xiàn)階段主要針對(duì)選擇的RDBMS定義E-R圖對(duì)應(yīng)的表,考慮屬性類型和范圍以及約束。
(五)物理階段
物理階段是一個(gè)驗(yàn)證并調(diào)優(yōu)的階段,是在實(shí)際物理設(shè)備上部署數(shù)據(jù)庫(kù),并進(jìn)行測(cè)試和調(diào)優(yōu)。
#p#
三 設(shè)計(jì)原則
(一)降低對(duì)數(shù)據(jù)庫(kù)功能的依賴
功能應(yīng)該由程序?qū)崿F(xiàn),而非DB實(shí)現(xiàn)。原因在于,如果功能由DB實(shí)現(xiàn)時(shí),一旦更換的DBMS不如之前的系統(tǒng)強(qiáng)大,不能實(shí)現(xiàn)某些功能,這時(shí)我們將不得不去修改代碼。所以,為了杜絕此類情況的發(fā)生,功能應(yīng)該有程序?qū)崿F(xiàn),數(shù)據(jù)庫(kù)僅僅負(fù)責(zé)數(shù)據(jù)的存儲(chǔ),以達(dá)到最低的耦合。
(二)定義實(shí)體關(guān)系的原則
當(dāng)定義一個(gè)實(shí)體與其他實(shí)體之間的關(guān)系時(shí),需要考量如下:
- 牽涉到的實(shí)體 識(shí)別出關(guān)系所涉及的所有實(shí)體。
- 所有權(quán) 考慮一個(gè)實(shí)體“擁有”另一個(gè)實(shí)體的情況。
- 基數(shù) 考量一個(gè)實(shí)體的實(shí)例和另一個(gè)實(shí)體實(shí)例關(guān)聯(lián)的數(shù)量。
關(guān)系與表數(shù)量
- 描述1:1關(guān)系最少需要1張表。
- 描述1:n關(guān)系最少需要2張表。
- 描述n:n關(guān)系最少需要3張表。
(三)列意味著唯一的值
如果表示坐標(biāo)(0,0),應(yīng)該使用兩列表示,而不是將“0,0”放在1個(gè)列中。
(四)列的順序
列的順序?qū)τ诒韥碚f無關(guān)緊要,但是從習(xí)慣上來說,采用“主鍵+外鍵+實(shí)體數(shù)據(jù)+非實(shí)體數(shù)據(jù)”這樣的順序?qū)α羞M(jìn)行排序顯然能得到比較好的可讀性。
(五)定義主鍵和外鍵
數(shù)據(jù)表必須定義主鍵和外鍵(如果有外鍵)。定義主鍵和外鍵不僅是RDBMS的要求,同時(shí)也是開發(fā)的要求。幾乎所有的代碼生成器都需要這些信息來生成常用方法的代碼(包括SQL文和引用),所以,定義主鍵和外鍵在開發(fā)階段是必須的。之所以說在開發(fā)階段是必須的是因?yàn)?,有不少團(tuán)隊(duì)出于性能考慮會(huì)在進(jìn)行大量測(cè)試后,在保證參照完整性不會(huì)出現(xiàn)大的缺陷后,會(huì)刪除掉DB的所有外鍵,以達(dá)到最優(yōu)性能。筆者認(rèn)為,在性能沒有出現(xiàn)問題時(shí)應(yīng)該保留外鍵,而即便性能真的出現(xiàn)問題,也應(yīng)該對(duì)SQL文進(jìn)行優(yōu)化,而非放棄外鍵約束。
(六)選擇鍵
1 人工鍵與自然鍵
人工健——實(shí)體的非自然屬性,根據(jù)需要由人強(qiáng)加的,如GUID,其對(duì)實(shí)體毫無意義;自然健——實(shí)體的自然屬性,如身份證編號(hào)。
人工鍵的好處:
- 鍵值永遠(yuǎn)不變
- 永遠(yuǎn)是單列存儲(chǔ)
人工鍵的缺點(diǎn):
- 因?yàn)槿斯ゆI是沒有實(shí)際意義的唯一值,所以不能通過人工鍵來避免重復(fù)行。
筆者建議全部使用人工鍵。原因如下:
- 在設(shè)計(jì)階段我們無法預(yù)測(cè)到代碼真正需要的值,所以干脆放棄猜測(cè)鍵,而使用人工鍵。
- 人工鍵復(fù)雜處理實(shí)體關(guān)系,而不負(fù)責(zé)任何屬性描述,這樣的設(shè)計(jì)使得實(shí)體關(guān)系與實(shí)體內(nèi)容得到高度解耦,這樣做的設(shè)計(jì)思路更加清晰。
筆者的另一個(gè)建議是——每張表都需要有一個(gè)對(duì)用戶而言有意義的自然鍵,在特殊情況下也許找不到這樣一個(gè)項(xiàng),此時(shí)可以使用復(fù)合鍵。這個(gè)鍵我在程序中并不會(huì)使用其作為唯一標(biāo)識(shí),但是卻可以在對(duì)數(shù)據(jù)庫(kù)直接進(jìn)行查詢時(shí)使用。
使用人工鍵的另一根弊端,主要源自對(duì)查詢性能的考量,因此選擇人工鍵的形式(列的類型)很重要:
- 自增值類型 由于類型輕巧查詢效率更好,但取值有限。
- GUID 查詢效率不如值類型,但是取值無限,且對(duì)開發(fā)人員更加親切。
2 智能健與非智能鍵
智能鍵——鍵值包含額外信息,其根據(jù)某種約定好的編碼規(guī)范進(jìn)行編碼,從鍵值本身可以獲取某些信息;非智能鍵,單純的無意義鍵值,如自增的數(shù)字或GUID。
智能鍵是一把雙刃劍,開發(fā)人員偏愛這種包含信息的鍵值,程序盼望著其中潛在的數(shù)據(jù);數(shù)據(jù)庫(kù)管理員或者設(shè)計(jì)者則討厭這種智能鍵,原因也是很顯然的,智能鍵對(duì)數(shù)據(jù)庫(kù)是潛在的風(fēng)險(xiǎn)。前面提到,數(shù)據(jù)庫(kù)設(shè)計(jì)的原則之一是不要把具有獨(dú)立意義的值的組合實(shí)現(xiàn)到一個(gè)單一的列中,應(yīng)該使用多個(gè)獨(dú)立的列。數(shù)據(jù)庫(kù)設(shè)計(jì)者,更希望開發(fā)人員通過拼接多個(gè)列來得到智能鍵,即以復(fù)合主鍵的形式給開發(fā)人員使用,而不是將一個(gè)列的值分解后使用。開發(fā)人員應(yīng)該接受這種數(shù)據(jù)庫(kù)設(shè)計(jì),但是很多開發(fā)者卻想不明白兩者的優(yōu)略。筆者認(rèn)為,使用單一列實(shí)現(xiàn)智能鍵存在這樣一個(gè)風(fēng)險(xiǎn),就是我們可能在設(shè)計(jì)階段無法預(yù)期到編碼規(guī)則可能會(huì)在后期發(fā)生變化。比如,構(gòu)成智能鍵的局部鍵的值用完而引起規(guī)則變化或者長(zhǎng)度變化,這種編碼規(guī)則的變化對(duì)于程序的有效性驗(yàn)證與智能鍵解析是破壞性的,這是系統(tǒng)運(yùn)維人員最不希望看到的。所以筆者建議如果需要智能鍵,請(qǐng)?jiān)跇I(yè)務(wù)邏輯層封裝(使用只讀屬性),不要再持久化層實(shí)現(xiàn),以避免上述問題。
(七)是否允許NULL
關(guān)于NULL我們需要了解它的幾個(gè)特性:
- 任何值和NULL拼接后都為NULL。
- 所有與NULL進(jìn)行的數(shù)學(xué)操作都返回NULL。
- 引入NULL后,邏輯不易處理。
那么我們是否應(yīng)該允許列為空呢?筆者認(rèn)為這個(gè)問題的答案受到我們的開發(fā)語言的影響。以C#為例,因?yàn)橐肓丝煽疹愋蛠硖幚頂?shù)據(jù)庫(kù)值類型為NULL的情形,所以是否允許為空對(duì)開發(fā)者來說意義并不大。但有一點(diǎn)必須注意,就是驗(yàn)證非空必須要在程序集進(jìn)行處理,而不該依賴于DBMS的非空約束,必須確保完整數(shù)據(jù)(所有必須的屬性均被賦值)到達(dá)DB(所謂的“安全區(qū)”,我們必須定義在多層系統(tǒng)中那些區(qū)域得到的數(shù)據(jù)是安全而純凈的)。
(八)屬性切割
一種錯(cuò)誤想法是,屬性與列是1:1的關(guān)系。對(duì)于開發(fā)者,我們公開屬性而非字段。舉個(gè)例子來說,對(duì)于實(shí)體“員工”有“名字”這一屬性,“名字”可以再被分解為“姓”和“名”,對(duì)于開發(fā)人員來說,顯然第二種數(shù)據(jù)結(jié)構(gòu)更受青睞(“姓” 和“名”作為兩個(gè)字段)。所以,在設(shè)計(jì)時(shí)我們也應(yīng)該根據(jù)需要考慮是否切割屬性。
#p#
(九)規(guī)范化——范式
當(dāng)筆者還在大學(xué)時(shí),范式是學(xué)習(xí)關(guān)系型數(shù)據(jù)庫(kù)時(shí)最頭疼的問題。我想也許會(huì)有讀者仍然不理解范式的價(jià)值,簡(jiǎn)單來說——范式將幫助我們來保證數(shù)據(jù)的有效性和完整性。規(guī)范化的目的如下:
- 消滅重復(fù)數(shù)據(jù)。
- 避免編寫不必要的,用來使重復(fù)數(shù)據(jù)同步的代碼。
- 保持表的瘦身,以及減從一張表中讀取數(shù)據(jù)時(shí)需要進(jìn)行的讀操作數(shù)量。
- 最大化聚集索引的使用,從而可以進(jìn)行更優(yōu)化的數(shù)據(jù)訪問和聯(lián)結(jié)。
- 減少每張表使用的索引數(shù)量,因?yàn)榫S護(hù)索引的成本很高。
規(guī)范化旨在——挑出復(fù)雜的實(shí)體,從中抽取出簡(jiǎn)單的實(shí)體。這個(gè)過程一直持續(xù)下去,直到數(shù)據(jù)庫(kù)中每個(gè)表都只代表一件事物,并且表中每個(gè)描述的都是這件事物為止。
1 規(guī)范化實(shí)體和屬性(去除冗余)
1NF:每個(gè)屬性都只應(yīng)表示一個(gè)單一的值,而非多個(gè)值。
需要考慮幾點(diǎn):
- 屬性是原子性的 需要考慮熟悉是否分解的足夠徹底,使得每個(gè)屬性都表示一個(gè)單一的值。(和“(三)列意味著唯一的值”描述的原則相同。)分解原則為——當(dāng)你需要分開處理每個(gè)部分時(shí)才分解值,并且分解到足夠用就行。(即使當(dāng)前不需要徹底分解屬性,也應(yīng)該考慮未來可能的需求變更。)
- 屬性的所有實(shí)例必須包含相同數(shù)量的值 實(shí)體有固定數(shù)量的屬性(表有固定數(shù)量的列)。設(shè)計(jì)實(shí)體時(shí),要讓每個(gè)屬性只有固定數(shù)量的值與其相關(guān)聯(lián)。
- 實(shí)體中出現(xiàn)的所有實(shí)體類型都必須不同
當(dāng)前設(shè)計(jì)不符合1NF的“臭味”:
- 包含分隔符類字符的字符串?dāng)?shù)據(jù)。
- 名字尾端有數(shù)字的屬性。
- 沒有定義鍵或鍵定義不好的表。
2 屬性間的關(guān)系(去除冗余)
2NF-實(shí)體必須符合1NF,每個(gè)屬性描述的東西都必須針對(duì)整個(gè)鍵(可以理解為oop中類型屬性的內(nèi)聚性)。
當(dāng)前設(shè)計(jì)不符合2NF的“臭味”:
- 重復(fù)的鍵屬性名字前綴(設(shè)計(jì)之外的數(shù)據(jù)冗余) 表明這些值可能描述了某些額外的實(shí)體。
- 有重復(fù)的數(shù)據(jù)組(設(shè)計(jì)之外的數(shù)據(jù)冗余) 這標(biāo)志著屬性間有函數(shù)依賴型。
- 沒有外鍵的復(fù)合主鍵 這標(biāo)志著鍵中的鍵值可能標(biāo)識(shí)了多種事物,而不是一種事物。
3NF-實(shí)體必須符合2NF,非鍵屬性不能描述其他非鍵屬性。(與2NF不同,3NF處理的是非鍵屬性和非鍵屬性之間的關(guān)系,而不是和鍵屬性之間的關(guān)系。
當(dāng)前設(shè)計(jì)不符合3NF的“臭味”:
- 多個(gè)屬性有同樣的前綴。
- 重復(fù)的數(shù)據(jù)組。
- 匯總的數(shù)據(jù),所引用的數(shù)據(jù)在一個(gè)完全不同的實(shí)體中。(有些人傾向于使用視圖,我更傾向于使用對(duì)象集合,即由程序來完成。)
BCNF-實(shí)體滿足第一范式,所有屬性完全依賴于某個(gè)鍵,如果所有的判定都是一個(gè)鍵,則實(shí)體滿足BCNF。(BCNF簡(jiǎn)單地?cái)U(kuò)展了以前的范式,它說的是:一個(gè)實(shí)體可能有若干個(gè)鍵,所有屬性都必須依賴于這些鍵中的一個(gè),也可以理解為“每個(gè)鍵必須唯一標(biāo)識(shí)實(shí)體,每個(gè)非鍵熟悉必須描述實(shí)體。”
3 去除實(shí)體組合鍵中的冗余
4NF-實(shí)體必須滿足BCNF,在一個(gè)屬性與實(shí)體的鍵之間,多值依賴(一條記錄在整個(gè)表的唯一性由多個(gè)值組合起來決定的)不能超過一個(gè)。
當(dāng)前設(shè)計(jì)不符合4NF的“臭味”:
- 三元關(guān)系(實(shí)體:實(shí)體:實(shí)體)。
- 潛伏的多值屬性。(如多個(gè)手機(jī)號(hào)。)
- 臨時(shí)數(shù)據(jù)或歷史值。(需要將歷史數(shù)據(jù)的主體提出,否則將存在大量冗余。)
4 盡量將所有關(guān)系分解為二元關(guān)系
5NF-實(shí)體必須滿足4NF,當(dāng)分解的信息無損的時(shí)候,確保所有關(guān)系都被分解為二元關(guān)系。
5NF保證在第四范式中存在的任何可以分解為實(shí)體的三元關(guān)系都被分解。有的三元關(guān)系可以在不丟失信息的前提下被分解為二元關(guān)系,當(dāng)分解為兩個(gè)二元關(guān)系的過程要丟失信息時(shí),關(guān)系被宣稱為處于第四范式中。所以,第五范式建議是,最好把現(xiàn)有的三元關(guān)系都分解為3個(gè)二元關(guān)系。
需要注意的是,規(guī)范化的結(jié)果可能是更多的表,更復(fù)雜的查詢。因此,處理到何種程度,取決于性能和數(shù)據(jù)架構(gòu)的多方考量。建議規(guī)范化到第四范式,原因是5NF的判斷太過隱晦。例如:表X(老師,學(xué)生,課程)是一個(gè)三元關(guān)系,可以分解為表A(老師,學(xué)生),表B(學(xué)生,課程),表C(老師,課程)。表X表示某個(gè)老師是上某個(gè)學(xué)生的某個(gè)課程的老師;表A表示老師教學(xué)生;表B表示學(xué)生上課;表C表示老師教課。單獨(dú)看是無法發(fā)現(xiàn)問題的,但是從數(shù)據(jù)出發(fā),"表X=表A+表B+表C"并不一定成立,即不能通過連接構(gòu)建分解前的數(shù)據(jù)。因?yàn)榭赡苡卸喾N組合,喪失了表X反饋出的業(yè)務(wù)規(guī)則。這種現(xiàn)象,容易在設(shè)計(jì)階段被忽略,但好在在開放階段會(huì)被顯現(xiàn),而且并不經(jīng)常發(fā)生。
推薦做法:
- 盡可能地遵守上述規(guī)范化原則。
- 所有屬性描述的都應(yīng)該是體現(xiàn)被建模實(shí)體的本質(zhì)的內(nèi)容。
- 至少必須有一個(gè)鍵,它唯一地標(biāo)識(shí)和描述了所建實(shí)體的本質(zhì)。
- 主鍵要謹(jǐn)慎選擇。
- 在邏輯階段能做多少規(guī)范化就做多少(性能不是邏輯階段考慮的范疇)。
(十)選擇數(shù)據(jù)類型(MS SQL 2008)
MS SQL的常用類型:
精確數(shù)字 | 不會(huì)發(fā)生精度損失 | bit tinyint smallint int bigint decimal |
近似數(shù)字 | 對(duì)于極值可能發(fā)生精度損失 | float(N) real |
日期和時(shí)間 | date time smalldatetime datetime datetime2 datetimeoffset | |
二進(jìn)制數(shù)據(jù) | bingary(N) varbinary(N) varbinary(max) | |
字符(串)數(shù)據(jù) | char(N) varchar(N) varchar(max) nchar(N) nvarchar(N) nvarchar(max) | |
存儲(chǔ)任意數(shù)據(jù) | sql_variant | |
時(shí)間戳 | timestamp | |
GUID | uniqueidentifier | |
XML | 不要試圖使用該類型規(guī)避1NF | xml |
空間數(shù)據(jù) | geometry geography | |
層次數(shù)據(jù) | heirarchyid |
MS SQL中不在支持的或糟糕的類型選擇
- image:被varbinary(max)取代。
- text和ntext:被varchar(max)和nvarchar(max)取代。
- money和smallmoney:開發(fā)過程中不好用,建議使用decimal。
常用類型選擇:
類型選擇的最基本規(guī)則是選擇滿足需要的最輕的類型,因?yàn)檫@樣查詢更快。
bool | 建議使用bit而非char(1),因?yàn)殚_發(fā)語言對(duì)其支持覺好,可以直接映射為bool或bool?。 |
大值數(shù)據(jù) | 使用所有備選類型中最小的那種,類型越大,查詢?cè)铰?,?dāng)字節(jié)大于8000時(shí),應(yīng)使用max。 |
主鍵 | 自增主鍵根據(jù)預(yù)期范圍選擇int或bigint,GUID使用uniqueidentifier而非varchar(N)。 |
(十一)優(yōu)化并行
設(shè)計(jì)DB時(shí)就應(yīng)該考慮到對(duì)并行進(jìn)行優(yōu)化,比如,MS SQL中的timestamp類型就是極好的選擇。
四 命名規(guī)則
- 表——“模塊名_表名”。表名最好不要用復(fù)數(shù),原因是在使用ORM框架開發(fā)時(shí),代碼生成器根據(jù)DB生成類定義,表生成了某個(gè)實(shí)例的類型定義,而不是實(shí)例集合。表名不要太長(zhǎng)。原因之一,某些軟件對(duì)表名最大長(zhǎng)度有限制;原因之二,使用代碼生成器往往會(huì)根據(jù)表名生產(chǎn)類型名稱,之后懶人會(huì)直接使用這一名稱,如果將太長(zhǎng)的名稱跨網(wǎng)絡(luò)邊界顯然不是明智之舉。
- 字段——bool類型用“Is”、“Can”、“Has”等表示;日期類型命名必須包含“Date”;時(shí)間類型必須包含“Time”。
- 存儲(chǔ)過程——使用“proc_”前綴。
- 視圖——使用“view_”前綴。
- 觸發(fā)器——使用“trig_”前綴。
原文鏈接:http://www.cnblogs.com/MeteorSeed/archive/2013/03/27/2880054.html