1. 程式人生 > >資料庫表結構設計淺談

資料庫表結構設計淺談

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

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

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

    合理利用欄位型別和長度。欄位型別儘可能反映真實的資料含義,滿足功能外欄位應該儘可能的短。    比如能用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個使用者表,登入操作顯然是很昂貴的,怎麼辦呢?分表,不是簡單的事情。


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