躲坑!數(shù)據(jù)庫設(shè)計(jì)的九大常見錯誤
譯文【51CTO.com快譯】引言:本文向您介紹在數(shù)據(jù)庫設(shè)計(jì)中常見的九種錯誤,希望能夠幫你在躲開這些坑點(diǎn)的同時,構(gòu)建出真正適合項(xiàng)目預(yù)期的數(shù)據(jù)庫。
作為一名數(shù)據(jù)庫設(shè)計(jì)人員,在您負(fù)責(zé)某個數(shù)據(jù)庫項(xiàng)目時,難免會在初期設(shè)計(jì)的過程中,以及將數(shù)據(jù)庫部署到生產(chǎn)環(huán)境之后,遇到不同的挑戰(zhàn)。其中的一些很可能源自數(shù)據(jù)庫的設(shè)計(jì)疏漏。也就是說,您在初期所做出的某些決策,很可能會對數(shù)據(jù)庫的最終運(yùn)行情況產(chǎn)生深遠(yuǎn)的影響。因此,為了躲避這些坑點(diǎn)的出現(xiàn),讓我們來討論一下數(shù)據(jù)庫設(shè)計(jì)中常見的九大錯誤吧。
1. 不佳的規(guī)劃
正如在蓋房子時,您不可能要求建筑工人在一小時之內(nèi)就鋪設(shè)好地基那樣,您需要事先規(guī)劃好房屋的設(shè)計(jì)圖紙。同理,您對數(shù)據(jù)庫的初始設(shè)計(jì)和計(jì)劃越周全,您的最終系統(tǒng)和服務(wù)的性能才會越好。
因此,一個具有前瞻性的數(shù)據(jù)庫,絕不是由某些臨時想法拼湊而成。不佳的設(shè)計(jì)很可能會導(dǎo)致數(shù)據(jù)庫本身的結(jié)構(gòu)性問題,而一旦它被部署到生產(chǎn)環(huán)境中,我們將面臨著替換和調(diào)整成本高昂的窘境。雖然我們無法預(yù)料到數(shù)據(jù)庫將來可能出現(xiàn)的每一個問題,但是良好的前期規(guī)劃,確實(shí)能降低推倒重來的風(fēng)險(xiǎn)。
2. 未能理解數(shù)據(jù)的意圖
無論是存儲個人數(shù)據(jù)的小型數(shù)據(jù)庫,還是處理復(fù)雜信息的大型企業(yè)級數(shù)據(jù)庫,每一種類型的創(chuàng)建與使用往往都是為了滿足某個明確的數(shù)據(jù)意圖。因此,設(shè)計(jì)人員只有真正理解了數(shù)據(jù)庫的使用目的,才能根據(jù)這些目標(biāo)進(jìn)行***匹配模式的設(shè)計(jì)。
可見,他們需要問出的關(guān)鍵問題包括:數(shù)據(jù)的性質(zhì)與類型、獲取的方式、存儲和檢索的頻率、數(shù)量、以及與之對應(yīng)的各種應(yīng)用程序。例如,那些需要在每日下班后手動錄入信息的數(shù)據(jù)庫,與能夠?qū)?shù)據(jù)實(shí)時自動地捕獲并存儲的工業(yè)級復(fù)雜數(shù)據(jù)庫相比,無論是在設(shè)計(jì)模型還是數(shù)據(jù)體量上,都是不一樣的。
因此,設(shè)計(jì)的關(guān)鍵就在于確保數(shù)據(jù)處置的效率、可用性和安全性(請參見“PostgreSQL安全性”, https://www.datasunrise.com/datasunrise-for-postgresql/)。倘若盲目地追求數(shù)據(jù)庫的大而全,反而是不切實(shí)際,而且不能夠滿足數(shù)據(jù)的具體應(yīng)用需求。
3. 標(biāo)準(zhǔn)化不足
數(shù)據(jù)庫設(shè)計(jì)并非是一個嚴(yán)格確定的過程。遵循相同設(shè)計(jì)規(guī)則的兩位開發(fā)人員,往往不一定會設(shè)計(jì)出相同的數(shù)據(jù)布局。這主要取決于各種軟件工程項(xiàng)目自身的創(chuàng)新性和數(shù)據(jù)庫在其中所扮演的作用。盡管如此,一些與設(shè)計(jì)有關(guān)的核心原則,還是能夠?qū)τ诖_保數(shù)據(jù)庫的***性能起到關(guān)鍵性作用的。而其中一項(xiàng)便是:規(guī)范化。數(shù)據(jù)的規(guī)范化特指:將數(shù)據(jù)表分解成為多個組成部分的技術(shù)。您可以持續(xù)進(jìn)行此項(xiàng)操作,直到每一個數(shù)據(jù)表都只表示一種事物,而每一列都只代表該事物的某一項(xiàng)屬性為止。
事實(shí)上,SQL主要就是建立在對于規(guī)范化數(shù)據(jù)集的讀取和操作基礎(chǔ)上的。您可以使用FROM子句從一個表中提取數(shù)據(jù),并使用JOIN將其添加到另一個表的內(nèi)容之中。您可以通過生成各種數(shù)據(jù)表來表示數(shù)據(jù)的類型。因此,SQL的附加功能對于數(shù)據(jù)庫的開發(fā)和性能都是至關(guān)重要的。
索引通過與鍵值的完全同步,來增強(qiáng)其效果。當(dāng)您必須使用LIKE、CHARINDEX、SUBSTRING、以及類似的命令來解析某一列的組合值時,SQL語句通過分解,以減少數(shù)據(jù)被搜索的次數(shù)。
因此,規(guī)范化數(shù)據(jù)庫對于簡易開發(fā)和保持高性能都是至關(guān)重要的。我們可以根據(jù)不同的標(biāo)準(zhǔn)化水平,來滿足數(shù)據(jù)庫相關(guān)記錄的插入、更新、查詢和刪除等需求。業(yè)界廣為接受的***實(shí)踐是:數(shù)據(jù)庫必須至少達(dá)到第三范式(Normal Form,3NF)的標(biāo)準(zhǔn)化水平。當(dāng)然,第四(4NF)和第五(5NF)范式也是非常實(shí)用且易于理解的。
4. 多余的記錄
多余的表和字段對于數(shù)據(jù)庫設(shè)計(jì)人員和管理員來說簡直是噩夢。它們消耗有限的系統(tǒng)資源,來保障系統(tǒng)整體的安全性、同步性和備份能力。特別是對于某些大型數(shù)據(jù)庫來說,它們的冗余字段記錄可能會達(dá)到幾百萬條,這對于計(jì)算資源的開銷顯然是相當(dāng)龐大的。它們在增加數(shù)據(jù)庫本身體積的同時,不但降低了系統(tǒng)的運(yùn)行效率,而且提高了數(shù)據(jù)受損的潛在風(fēng)險(xiǎn)。
在此,我們并不是說數(shù)據(jù)記錄的冗余沒有必要,而是說:應(yīng)當(dāng)根據(jù)規(guī)則與策略,在做好相關(guān)記錄的基礎(chǔ)上實(shí)現(xiàn)數(shù)據(jù)的冗余,并且在超過保存期限和適用條件時,由數(shù)據(jù)庫管理員及時予以刪除或銷毀。
5. 不佳的索引
無論是用戶還是應(yīng)用程序,都可能會需要查詢某個數(shù)據(jù)表的多個列值。然而,隨著表中行記錄的增加,相應(yīng)的查詢時間也會隨之穩(wěn)步上升。因此,為了加快查詢的速度、并減少由表容量所產(chǎn)生的影響,我們需要通過對數(shù)據(jù)表的列進(jìn)行索引,以便在調(diào)用SELECT查詢時,表中的相對應(yīng)記錄條目能夠?qū)崿F(xiàn)“秒回”。
不過對于SELECT函數(shù)的加速,通常會導(dǎo)致有更多INSERT、UPDATE和DELETE命令的產(chǎn)生。這很大程度上是因?yàn)椋核饕旧砭托枰粩嗟嘏c數(shù)據(jù)庫的內(nèi)容相同步,而此類操作就意味著會產(chǎn)生大量的數(shù)據(jù)庫引擎開銷。因此,頗具諷刺意味的是:您對加速SELECT查詢的嘗試,可能會反而導(dǎo)致數(shù)據(jù)庫整體速度的變慢。這正是過度索引的經(jīng)典案例。
解決上述問題的方法是:為所有列提供單一的索引,以用于查詢表中的不同主鍵。您也可以按照從最常用到最少使用的不同列,進(jìn)行降序排序。總之,構(gòu)建索引是門技術(shù)活,需要您去花時間總結(jié)和調(diào)整。
6. 所有域值的單一表
包羅萬象的域表(domain table)并非是數(shù)據(jù)庫設(shè)計(jì)的***方法。記住,關(guān)系型數(shù)據(jù)庫的構(gòu)建理念是:數(shù)據(jù)庫中的每個對象都只代表一個事物,不可出現(xiàn)任何指代不清的數(shù)據(jù)集。通過導(dǎo)航主鍵、表名、列名、以及各種關(guān)系,應(yīng)用能夠快速地解讀出數(shù)據(jù)集的含義。盡管如此,在進(jìn)行數(shù)據(jù)庫設(shè)計(jì)時,許多人總會情不自禁地滑向:數(shù)據(jù)表越來越多,數(shù)據(jù)庫越來越復(fù)雜和混亂的深淵。
過去,業(yè)界的普遍做法是將多張表壓縮到一張表之中,進(jìn)而簡化設(shè)計(jì)。但是這往往會帶來效率低下、且難以操作等問題。而且SQL代碼會隨著變長,可讀性也會有所下降。可見單一域表貌似一個抽象的文本容器,但它的確不是數(shù)據(jù)庫設(shè)計(jì)的***方式。
那么,作為規(guī)范化的一部分,我們需要通過數(shù)據(jù)隔離和分解的方法,讓每一行都只代表一個事物,同時讓多個域表相互區(qū)別不同的事物。其好處在于:
- 讓數(shù)據(jù)的查詢更加容易。
- 使用外鍵約束來更自然地驗(yàn)證數(shù)據(jù),而這正是單域表設(shè)計(jì)所無法實(shí)現(xiàn)的。因?yàn)樵趩斡虮碇校繌埍硭璧母鞣N鍵會變得難以維護(hù)。
- 每當(dāng)您想為某個對象添加多種數(shù)據(jù)時,您只需簡單地增加一到多列便可。
- 小型域表(small-domain tables)適合于存儲在硬盤的單頁上,而大型域表很可能會擴(kuò)展到多個磁盤的多個扇區(qū)里。顯然,將表存放在單頁中就意味著我們可以通過對單個磁盤的讀取,以快速提取數(shù)據(jù)。
- 因?yàn)檫@些域表很可能具有相同的底層用法與結(jié)構(gòu),因此擁有多個域表并不會妨礙您為所有的行啟用同一編輯器。
7.不佳或不一致的命名規(guī)則
數(shù)據(jù)庫設(shè)計(jì)人員和開發(fā)人員經(jīng)常過于關(guān)注自己眼前的技術(shù)。而那些命名規(guī)則之類的非技術(shù)方面,往往會被他們置于優(yōu)先級列表的***層,或甚至完全被忽略掉。而這恰恰容易造成災(zāi)難性的惡果。
客觀上說雖然命名規(guī)則可以由設(shè)計(jì)人員自行決定,但事實(shí)上,它對于數(shù)據(jù)庫文檔的重要程度還是不言而喻的(我們將在下面探討不佳的文檔)。由于數(shù)據(jù)庫設(shè)在系統(tǒng)中相對持久和穩(wěn)定,因此命名規(guī)則可以讓那些沒有參與過該構(gòu)建項(xiàng)目的人員(如:后繼的系統(tǒng)管理員、程序員、甚至是用戶),不需要翻閱上千頁的文檔,就能夠容易且快速地理解數(shù)據(jù)表與列的含義。當(dāng)然,關(guān)于如何準(zhǔn)確地命名各種數(shù)據(jù)表及其細(xì)節(jié),目前業(yè)界尚無定論。
因此,最重要的就是要講求“一貫性”。一旦您確定按照某個特定的方式來命名自己的對象,那么就請?jiān)谡麄€數(shù)據(jù)庫中貫徹該規(guī)則。對于數(shù)據(jù)表名稱而言,我們應(yīng)當(dāng)盡可能讓它能夠完整或簡約地描述所表示的對象,而每個列的名稱則能夠表示對象的屬性。對于簡單的數(shù)據(jù)庫來說,這并不難以被實(shí)現(xiàn);而對于需要構(gòu)建相互引用關(guān)系的復(fù)雜數(shù)據(jù)表而言,嚴(yán)格遵循命名規(guī)則就是一件比較繁瑣的事情了。
值得注意的是:此類規(guī)則不應(yīng)該對列名或表名的字符長度有過分的限制,而且要避免使用那些不易理解、或記憶的首字母縮略詞匯。例如:我們很難猜測出列名為CUST_DSCR的含義;而CUSTOMER_DESCRIPTION則會更好地表示出列名稱的意義。
同時,我們也要避免重復(fù)。如果表的名稱為“Students”,那么我們只需采用Name、 Address和Grade作為列名稱便可,而不必重復(fù)地使用StudentName、StudentAddress或StudentGrade。當(dāng)然,我們也不應(yīng)該擅自使用那些保留詞。如果您將某個列命名為“Index”,那么可能會給系統(tǒng)造成混淆,并產(chǎn)生不必要的錯誤。因此,我們可以使用諸如StudentIndex之類的描述性前綴。
8. 不佳的文檔
我們在上面提到過,數(shù)據(jù)庫的命名規(guī)則往往會體現(xiàn)在相關(guān)的文檔中。這些看似微不足道的文檔準(zhǔn)備工作,卻時常讓一些優(yōu)秀的數(shù)據(jù)庫設(shè)計(jì)“身敗名裂”。在系統(tǒng)運(yùn)營的過程中,不佳的文檔會極大地阻礙運(yùn)維團(tuán)隊(duì)進(jìn)行各種故障排除、架構(gòu)改進(jìn)、升級和連續(xù)性保證等工作。
數(shù)據(jù)庫設(shè)計(jì)者應(yīng)當(dāng)假想:如果自己某一天不再對自己的數(shù)據(jù)庫提供支持,那么他所準(zhǔn)備的相關(guān)文檔應(yīng)該能夠讓其他人輕松地接管后繼的設(shè)計(jì)、開發(fā)或管理工作。因此,良好的文檔必須包含對于各種列、表、關(guān)系和約束的定義,明示每一個元素的使用方式。如果您能適當(dāng)?shù)匕⒄f明某些預(yù)期值的范例,那就更具有參考價值了。
一些設(shè)計(jì)師可能會狹隘地將晦澀的文檔,作為確保自己工作安全的一種手段,以體現(xiàn)除了自己之外,其他人無法理解目標(biāo)數(shù)據(jù)庫的稀缺性。這種短見的做法不但很容易被管理層所識破,而且還會給自己若干年后的系統(tǒng)改造、與代碼改進(jìn)工作挖下不少的“坑”。
9.不充分的測試
無論是軟件研發(fā)也好,還是數(shù)據(jù)庫設(shè)計(jì)也罷,都離不開嚴(yán)格的測試檢驗(yàn)。可不幸的是,測試階段往往會由于項(xiàng)目截至日期的鄰近,而被無情地跳過。當(dāng)然,這是一種玩火自焚的做法。一些本該在測試階段被識別和解決的錯誤和不一致性,往往會給上線后的系統(tǒng)埋下各種隱患。
沒有用戶愿意使用、也沒有管理員愿意維護(hù)一個填充bug的數(shù)據(jù)庫。因此,在上線之前所進(jìn)行的各種深入且廣泛的數(shù)據(jù)庫測試,會大大減少部署到生產(chǎn)環(huán)境中所可能出現(xiàn)的故障數(shù)量和破壞程度。業(yè)界普遍認(rèn)為:好的測試不是要去發(fā)現(xiàn)每一個bug,而是幫助您找到并修正大多數(shù)潛在的問題。
總結(jié)
眾所周知,數(shù)據(jù)庫的開發(fā)和設(shè)計(jì)是任何數(shù)據(jù)密集型項(xiàng)目的核心,它與各種業(yè)務(wù)應(yīng)用都息息相關(guān)。因此,本文所列出的九種設(shè)計(jì)中的常見錯誤,都會在項(xiàng)目的推進(jìn)和系統(tǒng)的運(yùn)行中,對數(shù)據(jù)庫的后續(xù)性能產(chǎn)生嚴(yán)重的影響,進(jìn)而產(chǎn)生高昂的修復(fù)成本。因此,我們應(yīng)當(dāng)在設(shè)計(jì)的一開始就盡量避開這些坑點(diǎn),以構(gòu)建出真正適合項(xiàng)目預(yù)期的數(shù)據(jù)庫。
原文標(biāo)題:9 of the Most Common Mistakes in Database Design,作者:Mokhtar Ebrahim
【51CTO譯稿,合作站點(diǎn)轉(zhuǎn)載請注明原文譯者和出處為51CTO.com】

























