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