數據庫表結構設計淺談

 這篇文章如題所述,只打算談一下數據庫表本身設計,同時講到和表結構相關的性能和擴展性問題。下面講到的東西大多是從實際經驗中總結而來,算是對這項技術的一個反思。

   基本上在設計數據庫表的時候,首先考慮設計要滿足功能需求,這是最根本的,其次是滿足性能需求,再次則是滿足擴展性需求,這一點在大規模系統中是必須要考慮的。功能性需求比較容易滿足,下面我主要談談對性能和擴展性需求的一些設計方法。

    沒人不想速度更快,但是怎樣才能更快呢。設計高性能的表,我認爲主要需要做好:設計精簡合理的結構、減小數據量,具體的做法下面逐個分析。

    合理利用字段類型和長度。字段類型儘可能反映真實的數據含義,滿足功能外字段應該儘可能的短。    比如能用int字段的就不要用bigint,如果在某一個關係表裏只有兩個id字段,那麼bigint類型顯然比int類型的大了一倍。不同的數據庫系統裏面varchar和text類型在數據長度限制上不一樣,性能上也不一樣,選取要謹慎。標記位字段如果有bit就用bit類型,否則就用byte,用int就很浪費了(下面有一種特例)。

    選取高效的主鍵和索引。關於主鍵的選取,特別需要注意,因爲對錶中數據的讀取都直接或間接通過
主鍵,所以應該根據應用的特性設計滿足最接近數據存取順序的主鍵。例如數據讀取按照r1、r2、r3的順序,那麼他們的主鍵也最好是1、2、3的順序。有些人喜歡在關係表裏面也另外加一個主鍵字段,我認爲這樣算是浪費空間,而用關係ID作聯合主見更合理。


    索引的大小基本上由字段來決定,所以需要建立索引的字段應該簡化到最小。但是有些字段必須建立索引卻又無法簡化,這時候可以考慮用hash算法計算出較小的值作爲索引。例如url字段不適合做索引,但是可以用一個url_md5字段來存儲url的md5值來作爲索引,有效降低鍵值長度。
   
    減小數據量。除了縮小字段長度減小數據外,數據壓縮也是一個行之有效的辦法。目前有些數據庫引擎支持自動壓縮,相當方便,否則的自行通過程序壓縮、解壓也是可行的方案,壓縮對較長的文章、帖子性能提升顯著。壓縮還需要注意的一點就是內容太短,壓縮只會增加長度,壓縮過的內容無法再壓縮。

    精簡表結構。一個表複雜了不光處理起來更麻煩,而其性能也不好。如果一個表裏面有多部分(幾個字段合起來爲一部分)的字段並不同時存取,那麼這多部分字段應該根據存取特性分開爲多個表,這樣避免併發操作的鎖競爭。如果實在無法再分並且還是字段衆多,那麼可以把描述同一個對象的字段合併成一個字段存儲,有效降低字段數目,如果空字段較多時,這樣更能節省資源。例如,在customer表裏面company_name,company_phone等字段可以合併爲company字段,當然這樣做的前提是company_name字段不需要單獨作爲查詢條件(如果使用數據庫的xml技術,conpmay_name也可以作爲查詢條件)。

    適當採用冗餘字段,其實在我設計大部分表裏面是沒有冗餘字段的,並不是說冗餘字段不好,而是目前通過緩存系統可以適當代替冗餘字段的好處。冗餘字段主要是爲了避免多次關聯的查詢,但是如果關聯數據很容易被緩存,那麼查詢出主要數據後,關聯數據直接從緩存中讀取,這樣冗餘字段方案就可以被替代了。但是在緩存不利的情況下,冗餘字段確實是提升性能行之有效的辦法。

    其實影響數據庫性能的還有包括磁盤IO、內存、數據庫鎖、系統配置、數據庫配置、CPU性能等其他因素,但是這些並不在本文範疇。在大規模系統中,除了性能,可擴展性也是設計的關鍵字點,而數據庫表擴展性主要包含表邏輯結構、功能字段的增加、分表等。

    對於表的邏輯結構我遵循的設計原則:一個表只包含一個主要實體,如果主要實體中包含從屬實體數據,並且多個主要實體共享一個從屬實體,則把從屬實體單獨設計爲表,與主要實體關聯,這樣增加一個從屬實體增加單獨的表就行,不會影響以前的功能。如果主要實體不共享從屬實體,把從屬實體多個字段打包合併爲一個字段。合併字段的方式在上面也有提及,它不僅減少字段數目,而且讓在合併的字段中增加數據字段變得非常容易。
    在數據庫裏面經常用到標記位字段,取值只有0/1(true/false),有時候一個表裏有很多這樣的字段,這種情況下我認爲把所有標記爲字段合併到一個數字字段更好,數字中的每一位就表示一個標記位,例如用一個int型字段可以表示32個標記位。這可能帶來一些使用上的不便,不過卻大大增加了可擴展性。例如當16個標記位字段合併到int型字段後,還留下了16位的擴展餘地。並且用byte、int還是bigint可以隨取所需。

   增加表字段,好像也並不是難事,一條SQL而已。但是如果在Mysql裏面,修改表結構後引擎會導出再導入數據,在大數據量下(比如1000w、1億)增加字段變得幾乎不可能。對於這個問題,有人喜歡提前在表裏面多加一到多個保留字段,我個人比較反對這樣的做法:一是擴展性有限、二是命名太奇怪、三是類型不一定合適。我的設計原則:小表(比如50w行、100MB數據以內的表)不用特別考慮此擴展性問題,設計時只需要設計符合當前需求就可以,因爲即使以後對結構修改,也可以在很快的時間內完成。關係表等結構很穩定的表也不用考慮此問題。複雜的大表裏,首先確定核心的業務實體字段、外鍵和索引,而其他的字段則根據情況包合併到一個extra(xml或者字符串類型)的字段裏,這樣也就可以滿足了以後的擴展需求,因爲字符串或者xml結構裏增加數據字段是很容易的事情。

    分表(非分區,分區後並不會產生多個表,在部署上和分表會有不同,並非所有的數據庫版本都支持),也就是對錶垂直切分,得到結構相同的多個小表,是提升大表性能的首選方案。分表最基本的方法就是,固定法:根據ID特性把表拆分成固定的N個表、動態增長法:根據ID值分成等值區間任意多表、外鍵劃分法:根據外鍵值得特性劃分。如果ID增長沒有規律,那麼分表可採用固定法,基本算法爲:用ID對N取模或者獲取HASH(ID)的某部分字符串作爲表名的一部分。如果ID連續變化,則採用而動態增長法,基本算法爲:測試單表最合理的數據行數N,然後根據N作爲區間長度對ID拆分,拆分結果爲1-N,N+1-2N...。外鍵劃分法是根據外鍵值對錶進行劃分,基本的方法也就是固定法和動態增長法。不同的分表方法是由數據的特性和數據之間的關係決定的,例如需要根據URL查詢到文章,由於URL是無規律的,那麼分表方法可以爲固定法,按照URL的MD5值對錶進行劃分。例如論壇的帖子可以按照論壇板塊ID來分表,每個板塊一個表多個板塊一個表,這是外鍵劃分法。如果論壇和帖子是多對多關係,那麼帖子可以採用動態增長法分表,然後再把帖子和板塊關係表採用外鍵劃分法來分。這裏描述的方法算是比較基本的方法,而真實系統中分表情況要複雜的多,例如用戶表裏如果根據ID分表,但是又需要根據Email/密碼登錄,如果有10個用戶表,登錄操作顯然是很昂貴的,怎麼辦呢?分表,不是簡單的事情。


    關於數據庫設計心裏面想說的不少,但這裏也只是說了表的設計,並且只是簡單說了我認爲比較關鍵的地方,也只能算是淺談了。

發佈了36 篇原創文章 · 獲贊 5 · 訪問量 18萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章